/*******************************************************************************
 * Copyright (c) 2000, 2004 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
module dwt.internal.image.jpegfileformat;


private import dwt.dwt;

alias Exception StreamException;

private import dwt.internal.image.fileformat;

private import dwt.internal.image.ledatainputstream;
private import dwt.internal.image.ledataoutputstream;

private import dwt.graphics.color;
private import dwt.graphics.cursor;
private import dwt.graphics.device;
private import dwt.graphics.devicedata;
private import dwt.graphics.drawable;
private import dwt.graphics.font;
private import dwt.graphics.fontdata;
private import dwt.graphics.fontmetrics;
private import dwt.graphics.gc;
private import dwt.graphics.gcdata;
private import dwt.graphics.image;
private import dwt.graphics.imagedata;
private import dwt.graphics.imageloader;
private import dwt.graphics.imageloaderevent;
private import dwt.graphics.imageloaderlistener;
private import dwt.graphics.palettedata;
private import dwt.graphics.point;
private import dwt.graphics.rectangle;
private import dwt.graphics.region;
private import dwt.graphics.rgb;
private import dwt.graphics.textlayout;
private import dwt.graphics.textstyle;



private import dwt.util.javatypes;
private import dwt.util.util;
private import dwt.util.vector;
private import dwt.util.hashtable;
private import dwt.util.eventhandler;



class JPEGAppn : JPEGVariableSizeSegment {

	public this(byte[] reference) {
		super(reference);
	}
	
	public this(LEDataInputStream byteStream) {
		super(byteStream);
	}
	
	public boolean verify() {
		int marker = getSegmentMarker();
		return marker >= JPEGFileFormat.APP0 && marker <= JPEGFileFormat.APP15;
	}
}



class JPEGArithmeticConditioningTable : JPEGVariableSizeSegment {

	public this(LEDataInputStream byteStream) {
		super(byteStream);
	}
	
	public int signature() {
		return JPEGFileFormat.DAC;
	}
}




class JPEGComment : JPEGVariableSizeSegment {

	public this(byte[] reference) {
		super(reference);
	}
	
	public this(LEDataInputStream byteStream) {
		super(byteStream);
	}
	
	public int signature() {
		return JPEGFileFormat.COM;
	}
}



class JPEGEndOfImage : JPEGFixedSizeSegment {

	public this() {
		super();
	}
	
	public this(byte[] reference) {
		super(reference);
	}
	
	public int signature() {
		return JPEGFileFormat.EOI;
	}
	
	public int fixedSize() {
		return 2;
	}
}









class JPEGFileFormat : FileFormat {
	int restartInterval;
	JPEGFrameHeader frameHeader;
	int imageWidth, imageHeight;
	int interleavedMcuCols, interleavedMcuRows;
	int maxV, maxH;
	boolean progressive;
	int samplePrecision;
	int nComponents;
	int[][] frameComponents;
	int[] componentIds;
	byte[][] imageComponents;
	int[] dataUnit;
	int[][][] dataUnits;
	int[] precedingDCs;
	JPEGScanHeader scanHeader;
	byte[] dataBuffer;
	int currentBitCount;
	int bufferCurrentPosition;
	int restartsToGo;
	int nextRestartNumber;
	JPEGArithmeticConditioningTable arithmeticTables;
	JPEGHuffmanTable[] acHuffmanTables;
	JPEGHuffmanTable[] dcHuffmanTables;
	int[][] quantizationTables;
	int currentByte;
	int decoderQFactor;
	int encoderQFactor = 75;
	int eobrun = 0;
	/* JPEGConstants */
	public static const int DCTSIZE = 8;
	public static const int DCTSIZESQR = 64;
	/* JPEGFixedPointConstants */
	public static const int FIX_0_899976223 = 7373;
	public static const int FIX_1_961570560 = 16069;
	public static const int FIX_2_053119869 = 16819;
	public static const int FIX_0_298631336 = 2446;
	public static const int FIX_1_847759065 = 15137;
	public static const int FIX_1_175875602 = 9633;
	public static const int FIX_3_072711026 = 25172;
	public static const int FIX_0_765366865 = 6270;
	public static const int FIX_2_562915447 = 20995;
	public static const int FIX_0_541196100 = 4433;
	public static const int FIX_0_390180644 = 3196;
	public static const int FIX_1_501321110 = 12299;
	/* JPEGMarkerCodes */
	public static const int APP0  = 0xFFE0;
	public static const int APP15 = 0xFFEF;
	public static const int COM   = 0xFFFE;
	public static const int DAC   = 0xFFCC;
	public static const int DHP   = 0xFFDE;
	public static const int DHT   = 0xFFC4;
	public static const int DNL   = 0xFFDC;
	public static const int DRI   = 0xFFDD;
	public static const int DQT   = 0xFFDB;
	public static const int EOI   = 0xFFD9;
	public static const int EXP   = 0xFFDF;
	public static const int JPG   = 0xFFC8;
	public static const int JPG0  = 0xFFF0;
	public static const int JPG13 = 0xFFFD;
	public static const int RST0  = 0xFFD0;
	public static const int RST1  = 0xFFD1;
	public static const int RST2  = 0xFFD2;
	public static const int RST3  = 0xFFD3;
	public static const int RST4  = 0xFFD4;
	public static const int RST5  = 0xFFD5;
	public static const int RST6  = 0xFFD6;
	public static const int RST7  = 0xFFD7;
	public static const int SOF0  = 0xFFC0;
	public static const int SOF1  = 0xFFC1;
	public static const int SOF2  = 0xFFC2;
	public static const int SOF3  = 0xFFC3;
	public static const int SOF5  = 0xFFC5;
	public static const int SOF6  = 0xFFC6;
	public static const int SOF7  = 0xFFC7;
	public static const int SOF9  = 0xFFC9;
	public static const int SOF10 = 0xFFCA;
	public static const int SOF11 = 0xFFCB;
	public static const int SOF13 = 0xFFCD;
	public static const int SOF14 = 0xFFCE;
	public static const int SOF15 = 0xFFCF;
	public static const int SOI   = 0xFFD8;
	public static const int SOS   = 0xFFDA;
	public static const int TEM   = 0xFF01;
	/* JPEGFrameComponentParameterConstants */
	public static const int TQI	= 0;
	public static const int HI	= 1;
	public static const int VI	= 2;
	public static const int CW	= 3;
	public static const int CH	= 4;
	/* JPEGScanComponentParameterConstants */
	public static const int DC	= 0;
	public static const int AC	= 1;
	/* JFIF Component Constants */
	public static const int ID_Y		= 1 - 1;
	public static const int ID_CB	= 2 - 1;
	public static const int ID_CR	= 3 - 1;

	public static int[] ExtendTest = [
		0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 
		4096, 8192, 16384, 32768, 65536, 131072, 262144
	];
	public static int[] ExtendOffset = [
		0, -1, -3, -7, -15, -31, -63, -127, -255, -511, -1023, -2047, 
		-4095, -8191, -16383, -32767, -65535, -131071, -262143
	];
	public static int[] ZigZag8x8 = [
		0, 1, 8, 16, 9, 2, 3, 10,
		17, 24, 32, 25, 18, 11, 4, 5,
		12, 19, 26, 33, 40, 48, 41, 34,
		27, 20, 13, 6, 7, 14, 21, 28,
		35, 42, 49, 56, 57, 50, 43, 36,
		29, 22, 15, 23, 30, 37, 44, 51,
		58, 59, 52, 45, 38, 31, 39, 46,
		53, 60, 61, 54, 47, 55, 62, 63
	];
	public static int[] CrRTable, CbBTable, CrGTable, CbGTable;
	public static int[] RYTable, GYTable, BYTable,
		RCbTable, GCbTable, BCbTable, RCrTable, GCrTable, BCrTable, NBitsTable;
	private static boolean staticCtor_done = false;

static void staticCtor() {
		if(staticCtor_done) return;
		staticCtor_done = true;
	
		initialize();
	}

this(){
	staticCtor();
}

void compress(ImageData image, byte[] dataYComp, byte[] dataCbComp, byte[] dataCrComp) {
	int srcWidth = image.width;
	int srcHeight = image.height;
	int vhFactor = maxV * maxH;
	int[] frameComponent;
	imageComponents = new byte[][nComponents];
	for (int i = 0; i < nComponents; i++) {
		frameComponent = frameComponents[componentIds[i]];
		imageComponents[i] = new byte[frameComponent[CW] * frameComponent[CH]];
	}
	frameComponent = frameComponents[componentIds[ID_Y]];
	for (int yPos = 0; yPos < srcHeight; yPos++) {
		int srcOfs = yPos * srcWidth;
		int dstOfs = yPos * frameComponent[CW];
		System.arraycopy(dataYComp, srcOfs, imageComponents[ID_Y], dstOfs, srcWidth);
	}
	frameComponent = frameComponents[componentIds[ID_CB]];
	for (int yPos = 0; yPos < srcHeight / maxV; yPos++) {
		int destRowIndex = yPos * frameComponent[CW];
		for (int xPos = 0; xPos < srcWidth / maxH; xPos++) {
			int sum = 0;
			for (int iv = 0; iv < maxV; iv++) {
				int srcIndex = (yPos * maxV + iv) * srcWidth + (xPos * maxH);
				for (int ih = 0; ih < maxH; ih++) {
					sum += dataCbComp[srcIndex + ih] & 0xFF;
				}
			}
			imageComponents[ID_CB][destRowIndex + xPos] = cast(byte)(sum / vhFactor);
		}
	}
	frameComponent = frameComponents[componentIds[ID_CR]];
	for (int yPos = 0; yPos < srcHeight / maxV; yPos++) {
		int destRowIndex = yPos * frameComponent[CW];
		for (int xPos = 0; xPos < srcWidth / maxH; xPos++) {
			int sum = 0;
			for (int iv = 0; iv < maxV; iv++) {
				int srcIndex = (yPos * maxV + iv) * srcWidth + (xPos * maxH);
				for (int ih = 0; ih < maxH; ih++) {
					sum += dataCrComp[srcIndex + ih] & 0xFF;
				}
			}
			imageComponents[ID_CR][destRowIndex + xPos] = cast(byte)(sum / vhFactor);
		}
	}
	for (int iComp = 0; iComp < nComponents; iComp++) {
		byte[] imageComponent = imageComponents[iComp];
		frameComponent = frameComponents[componentIds[iComp]];
		int hFactor = frameComponent[HI];
		int vFactor = frameComponent[VI];
		int componentWidth = frameComponent[CW];
		int componentHeight = frameComponent[CH];
		int compressedWidth = srcWidth / (maxH / hFactor);
		int compressedHeight = srcHeight / (maxV / vFactor);
		if (compressedWidth < componentWidth) {
			int delta = componentWidth - compressedWidth;
			for (int yPos = 0; yPos < compressedHeight; yPos++) {
				int dstOfs = ((yPos + 1) * componentWidth - delta);
				int dataValue = imageComponent[dstOfs - 1] & 0xFF;
				for (int i = 0; i < delta; i++) {
					imageComponent[dstOfs + i] = cast(byte)dataValue;
				}
			}
		}
		if (compressedHeight < componentHeight) {
			int srcOfs = (compressedHeight - 1) * componentWidth;
			for (int yPos = compressedHeight; yPos <= componentHeight; yPos++) {
				int dstOfs = (yPos - 1) * componentWidth;
				System.arraycopy(imageComponent, srcOfs, imageComponent, dstOfs, componentWidth);
			}
		}
	}
}
void convert4BitRGBToYCbCr(ImageData image) {
	DWTRGB[] rgbs = image.getRGBs();
	int paletteSize = rgbs.length;
	byte[] yComp = new byte[paletteSize];
	byte[] cbComp = new byte[paletteSize];
	byte[] crComp = new byte[paletteSize];
	int srcWidth = image.width;
	int srcHeight = image.height;
	for (int i = 0; i < paletteSize; i++) {
		DWTRGB color = rgbs[i];
		int r = color.red;
		int g = color.green;
		int b = color.blue;
		int n = RYTable[r] + GYTable[g] + BYTable[b];
		yComp[i] = cast(byte)(n / 65536);
		if ((n < 0) && (n % 65536 != 0)) yComp[i]--;
		n = RCbTable[r] + GCbTable[g] + BCbTable[b];
		cbComp[i] = cast(byte)(n / 65536);
		if ((n < 0) && (n % 65536 != 0)) cbComp[i]--;
		n = RCrTable[r] + GCrTable[g] + BCrTable[b];
		crComp[i] = cast(byte)(n / 65536);
		if ((n < 0) && (n % 65536 != 0)) crComp[i]--;
	}
	int bSize = srcWidth * srcHeight;
	byte[] dataYComp = new byte[bSize];
	byte[] dataCbComp = new byte[bSize];
	byte[] dataCrComp = new byte[bSize];
	byte[] origData = image.data;
	for (int yPos = 0; yPos < srcHeight; yPos++) {
		for (int xPos = 0; xPos < srcWidth / 2; xPos++) {
			int srcIndex = yPos * (srcWidth / 2) + xPos;
			int dstIndex = yPos * srcWidth + (xPos * 2);
			int value2 = origData[srcIndex] & 0xFF;
			int value1 = value2 / 16;
			value2 = value2 % 16;
			dataYComp[dstIndex] = yComp[value1];
			dataCbComp[dstIndex] = cbComp[value1];
			dataCrComp[dstIndex] = crComp[value1];
			dataYComp[dstIndex + 1] = yComp[value2];
			dataCbComp[dstIndex + 1] = cbComp[value2];
			dataCrComp[dstIndex + 1] = crComp[value2];
		}
	}
	compress(image, dataYComp, dataCbComp, dataCrComp);
}
void convert8BitRGBToYCbCr(ImageData image) {
	DWTRGB[] rgbs = image.getRGBs();
	int paletteSize = rgbs.length;
	byte[] yComp = new byte[paletteSize];
	byte[] cbComp = new byte[paletteSize];
	byte[] crComp = new byte[paletteSize];
	int srcWidth = image.width;
	int srcHeight = image.height;
	for (int i = 0; i < paletteSize; i++) {
		DWTRGB color = rgbs[i];
		int r = color.red;
		int g = color.green;
		int b = color.blue;
		int n = RYTable[r] + GYTable[g] + BYTable[b];
		yComp[i] = cast(byte)(n / 65536);
		if ((n < 0) && (n % 65536 != 0)) yComp[i]--;
		n = RCbTable[r] + GCbTable[g] + BCbTable[b];
		cbComp[i] = cast(byte)(n / 65536);
		if ((n < 0) && (n % 65536 != 0)) cbComp[i]--;
		n = RCrTable[r] + GCrTable[g] + BCrTable[b];
		crComp[i] = cast(byte)(n / 65536);
		if ((n < 0) && (n % 65536 != 0)) crComp[i]--;
	}
	int dstWidth = image.width;
	int dstHeight = srcHeight;
	int stride = (srcWidth + 3) / 4 * 4;
	int bSize = dstWidth * dstHeight;
	byte[] dataYComp = new byte[bSize];
	byte[] dataCbComp = new byte[bSize];
	byte[] dataCrComp = new byte[bSize];
	byte[] origData = image.data;
	for (int yPos = 0; yPos < srcHeight; yPos++) {
		int srcRowIndex = yPos * stride;
		int dstRowIndex = yPos * dstWidth;
		for (int xPos = 0; xPos < srcWidth; xPos++) {
			int value = origData[srcRowIndex + xPos] & 0xFF;
			int dstIndex = dstRowIndex + xPos;
			dataYComp[dstIndex] = yComp[value];
			dataCbComp[dstIndex] = cbComp[value];
			dataCrComp[dstIndex] = crComp[value];
		}
	}
	compress(image, dataYComp, dataCbComp, dataCrComp);
}
byte[] convertCMYKToRGB() {
	/* Unsupported CMYK format. Answer an empty ubyte array. */
	return new byte[0];
}
void convertImageToYCbCr(ImageData image) {
	switch (image.depth) {
		case 4:
			convert4BitRGBToYCbCr(image);
			return;
		case 8:
			convert8BitRGBToYCbCr(image);
			return;
		case 16:
		case 24:
		case 32:
			convertMultiRGBToYCbCr(image);
			return;
		default:
			DWT.error(__FILE__, __LINE__, DWT.ERROR_UNSUPPORTED_DEPTH);
	}
	return;
}
void convertMultiRGBToYCbCr(ImageData image) {
	int srcWidth = image.width;
	int srcHeight = image.height;
	int bSize = srcWidth * srcHeight;
	byte[] dataYComp = new byte[bSize];
	byte[] dataCbComp = new byte[bSize];
	byte[] dataCrComp = new byte[bSize];
	PaletteData palette = image.palette;
	int[] buffer = new int[srcWidth];
	if (palette.isDirect) {
		int redMask = palette.redMask;
		int greenMask = palette.greenMask;
		int blueMask = palette.blueMask;
		int redShift = palette.redShift;
		int greenShift = palette.greenShift;
		int blueShift = palette.blueShift;
		for (int yPos = 0; yPos < srcHeight; yPos++) {
			image.getPixels(0, yPos, srcWidth, buffer, 0);
			int dstRowIndex = yPos * srcWidth;
			for (int xPos = 0; xPos < srcWidth; xPos++) {
				int pixel = buffer[xPos];
				int dstDataIndex = dstRowIndex + xPos;
				int r = pixel & redMask;
				r = (redShift < 0) ? r >>> -redShift : r << redShift;
				int g = pixel & greenMask;
				g = (greenShift < 0) ? g >>> -greenShift : g << greenShift;
				int b = pixel & blueMask;
				b = (blueShift < 0) ? b >>> -blueShift : b << blueShift;				
				dataYComp[dstDataIndex] = cast(byte)((RYTable[r] + GYTable[g] + BYTable[b]) / 65536);
				dataCbComp[dstDataIndex] = cast(byte)((RCbTable[r] + GCbTable[g] + BCbTable[b]) / 65536);
				dataCrComp[dstDataIndex] = cast(byte)((RCrTable[r] + GCrTable[g] + BCrTable[b]) / 65536);
			}
		}
	} else {
		for (int yPos = 0; yPos < srcHeight; yPos++) {
			image.getPixels(0, yPos, srcWidth, buffer, 0);
			int dstRowIndex = yPos * srcWidth;
			for (int xPos = 0; xPos < srcWidth; xPos++) {
				int pixel = buffer[xPos];
				int dstDataIndex = dstRowIndex + xPos;
				DWTRGB rgb = palette.getRGB(pixel);
				int r = rgb.red;
				int g = rgb.green;
				int b = rgb.blue;
				dataYComp[dstDataIndex] = cast(byte)((RYTable[r] + GYTable[g] + BYTable[b]) / 65536);
				dataCbComp[dstDataIndex] = cast(byte)((RCbTable[r] + GCbTable[g] + BCbTable[b]) / 65536);
				dataCrComp[dstDataIndex] = cast(byte)((RCrTable[r] + GCrTable[g] + BCrTable[b]) / 65536);
			}
		}
	}
	compress(image, dataYComp, dataCbComp, dataCrComp);
}
byte[] convertYToRGB() {
	int compWidth = frameComponents[componentIds[ID_Y]][CW];
	int bytesPerLine = (((imageWidth * 8 + 7) / 8) + 3) / 4 * 4;
	byte[] data = new byte[bytesPerLine * imageHeight];
	byte[] yComp = imageComponents[ID_Y];
	int destIndex = 0;
	for (int i = 0; i < imageHeight; i++) {
		int srcIndex = i * compWidth;
		for (int j = 0; j < bytesPerLine; j++) {
			int y = yComp[srcIndex] & 0xFF;
			if (y < 0) {
				y = 0;
			} else {
				if (y > 255) y = 255;
			}
			if (j >= imageWidth) {
				y = 0;
			}
			data[destIndex] = cast(byte)y;
			srcIndex++;
			destIndex++;
		}
	}
	return data;
}
byte[] convertYCbCrToRGB() {
	/**
	 * Convert existing image components into an RGB format.
	 * YCbCr is defined per CCIR 601-1, except that Cb and Cr are
	 * normalized to the range 0..MAXJSAMPLE rather than -0.5 .. 0.5.
	 * The conversion equations to be implemented are therefore
	 * 	R = Y                + 1.40200 * Cr
	 * 	G = Y - 0.34414 * Cb - 0.71414 * Cr
	 * 	B = Y + 1.77200 * Cb
	 * where Cb and Cr represent the incoming values less MAXJSAMPLE/2.
	 * (These numbers are derived from TIFF 6.0 section 21, dated 3-June-92.)
	 * 
	 * To avoid floating-point arithmetic, we represent the fractional constants
	 * as integers scaled up by 2^16 (about 4 digits precision); we have to divide
	 * the products by 2^16, with appropriate rounding, to get the correct answer.
	 * Notice that Y, being an integral input, does not contribute any fraction
	 * so it need not participate in the rounding.
	 * 
	 * For even more speed, we avoid doing any multiplications in the inner loop
	 * by precalculating the constants times Cb and Cr for all possible values.
	 * For 8-bit JSAMPLEs this is very reasonable (only 256 entries per table);
	 * for 12-bit samples it is still acceptable.  It's not very reasonable for
	 * 16-bit samples, but if you want lossless storage you shouldn't be changing
	 * colorspace anyway.
	 * The Cr=>R and Cb=>B values can be rounded to integers in advance; the
	 * values for the G calculation are left scaled up, since we must add them
	 * together before rounding.
	 */
	int bSize = imageWidth * imageHeight * nComponents;
	byte[] rgbData = new byte[bSize];
	int destIndex = 0;
	expandImageComponents();
	byte[] yComp = imageComponents[ID_Y];
	byte[] cbComp = imageComponents[ID_CB];
	byte[] crComp = imageComponents[ID_CR];
	int compWidth = frameComponents[componentIds[ID_Y]][CW];
	for (int v = 0; v < imageHeight; v++) {
		int srcIndex = v * compWidth;
		for (int i = 0; i < imageWidth; i++) {
			int y = yComp[srcIndex] & 0xFF;
			int cb = cbComp[srcIndex] & 0xFF;
			int cr = crComp[srcIndex] & 0xFF;
			int r = y + CrRTable[cr];
			int g = y + ((CbGTable[cb] + CrGTable[cr]) / 65536);
			int b = y + CbBTable[cb];
			if (r < 0) {
				r = 0;
			} else {
				if (r > 255) r = 255;
			}
			if (g < 0) {
				g = 0;
			} else {
				if (g > 255) g = 255;
			}
			if (b < 0) {
				b = 0;
			} else {
				if (b > 255) b = 255;
			}
			rgbData[destIndex] = cast(byte)b;
			rgbData[destIndex + 1] = cast(byte)g;
			rgbData[destIndex + 2] = cast(byte)r;
			destIndex += 3;
			srcIndex++;
		}
	}
	return rgbData;
}
byte[] convertYIQToRGB() {
	/* Unsupported CMYK format. Answer an empty ubyte array. */
	return new byte[0];
}
void decodeACCoefficients(int[] dataUnit, int iComp) {
	int[] sParams = scanHeader.componentParameters[componentIds[iComp]];
	JPEGHuffmanTable acTable = acHuffmanTables[sParams[AC]];
	int k = 1;
	while (k < 64) {
		int rs = decodeUsingTable(acTable);
		int r = rs >> 4;
		int s = rs & 0xF;
		if (s == 0) {
			if (r == 15) {
				k += 16;
			} else {
				break;
			}
		} else {
			k += r;
			int bits = receive(s);
			dataUnit[ZigZag8x8[k]] = extendBy(bits, s);
			k++;
		}
	}
}
void decodeACFirstCoefficients(int[] dataUnit, int iComp, int start, int end, int approxBit) {
	if (eobrun > 0) {
		eobrun--;
		return;
	}
	int[] sParams = scanHeader.componentParameters[componentIds[iComp]];
	JPEGHuffmanTable acTable = acHuffmanTables[sParams[AC]];
	int k = start;
	while (k <= end) {
		int rs = decodeUsingTable(acTable);
		int r = rs >> 4;
		int s = rs & 0xF;
		if (s == 0) {
			if (r == 15) {
				k += 16;
			} else {
				eobrun = (1 << r) + receive(r) - 1;
				break;
			}
		} else {
			k += r;
			int bits = receive(s);
			dataUnit[ZigZag8x8[k]] = extendBy(bits, s) << approxBit;
			k++;
		}
	}
}
void decodeACRefineCoefficients(int[] dataUnit, int iComp, int start, int end, int approxBit) {
	int[] sParams = scanHeader.componentParameters[componentIds[iComp]];
	JPEGHuffmanTable acTable = acHuffmanTables[sParams[AC]];
	int k = start;
	while (k <= end) {
		if (eobrun > 0) {
			while (k <= end) {
				int zzIndex = ZigZag8x8[k];
				if (dataUnit[zzIndex] != 0) {
					dataUnit[zzIndex] = refineAC(dataUnit[zzIndex], approxBit);
				}
				k++;
			}
			eobrun--;
		} else {
			int rs = decodeUsingTable(acTable);
			int r = rs >> 4;
			int s = rs & 0xF;
			if (s == 0) {
				if (r == 15) {
					int zeros = 0;
					while (zeros < 16 && k <= end) {
						int zzIndex = ZigZag8x8[k];
						if (dataUnit[zzIndex] != 0) {
							dataUnit[zzIndex] = refineAC(dataUnit[zzIndex], approxBit);
						} else {
							zeros++;
						}
						k++;
					}
				} else {
					eobrun = (1 << r) + receive(r);
				}
			} else {
				int bit_renamed = receive(s);
				int zeros = 0;
				int zzIndex = ZigZag8x8[k];
				while ((zeros < r || dataUnit[zzIndex] != 0) && k <= end) {
					if (dataUnit[zzIndex] != 0) {
						dataUnit[zzIndex] = refineAC(dataUnit[zzIndex], approxBit);
					} else {
						zeros++;
					}
					k++;
					zzIndex = ZigZag8x8[k];
				}
				if (bit_renamed != 0) {
					dataUnit[zzIndex] = 1 << approxBit;
				} else {
					dataUnit[zzIndex] = -1 << approxBit;
				}
				k++;
			}
		}
	}
}
int refineAC(int ac, int approxBit) {
	if (ac > 0) {
		int bit_renamed = nextBit();
		if (bit_renamed != 0) {
			ac = ac + (1 << approxBit);
		}
	} else if (ac < 0) {
		int bit_renamed = nextBit();
		if (bit_renamed != 0) {
			ac = ac + (-1 << approxBit);
		}
	}
	return ac;
}
void decodeDCCoefficient(int[] dataUnit, int iComp, boolean first, int approxBit) {
	int[] sParams = scanHeader.componentParameters[componentIds[iComp]];
	JPEGHuffmanTable dcTable = dcHuffmanTables[sParams[DC]];
	int lastDC = 0;
	if (progressive && !first) {
		int bit_renamed = nextBit();
		lastDC = dataUnit[0] + (bit_renamed << approxBit);
	} else {
		lastDC = precedingDCs[iComp];
		int nBits = decodeUsingTable(dcTable);
		if (nBits != 0) {
			int bits = receive(nBits);
			int diff = extendBy(bits, nBits);
			lastDC = lastDC + diff;
			precedingDCs[iComp] = lastDC;
		}
		if (progressive) {
			lastDC = lastDC << approxBit;
		}
	}
	dataUnit[0] = lastDC;
}
void dequantize(int[] dataUnit, int iComp) {
	int[] qTable = quantizationTables[frameComponents[componentIds[iComp]][TQI]];
	for (int i = 0; i < dataUnit.length; i++) {
		int zzIndex = ZigZag8x8[i];
		dataUnit[zzIndex] = dataUnit[zzIndex] * qTable[i];
	}
}
byte[] decodeImageComponents() {
	int[] compIds = new int[nComponents];
	int compIdsIndex = 0;
	for (int i = 0; i < nComponents; i++) {
		compIds[compIdsIndex] = i + 1;
		compIdsIndex++;
	}
	if ((compIds.length == 3) &&
		(compIds[0] == 1) &&
		(compIds[1] == 2) &&
		(compIds[2] == 3)) {
			return convertYCbCrToRGB();
	}
	if ((compIds.length == 3) &&
		(compIds[0] == 1) &&
		(compIds[1] == 4) &&
		(compIds[2] == 5)) {
			return convertYIQToRGB();
	}
	if (compIds.length == 4) {
		return convertCMYKToRGB();
	}
	return convertYToRGB();
}
void decodeMCUAtXAndY(int xmcu, int ymcu, int nComponentsInScan, boolean first, int start, int end, int approxBit) {
	for (int iComp = 0; iComp < nComponentsInScan; iComp++) {
		int scanComponent = iComp;
		while (scanHeader.componentParameters[componentIds[scanComponent]] is null) {
			scanComponent++;
		}
		int[] frameComponent = frameComponents[componentIds[scanComponent]];
		int hi = frameComponent[HI];
		int vi = frameComponent[VI];
		if (nComponentsInScan == 1) {
			hi = 1;
			vi = 1;
		}
		int compWidth = frameComponent[CW];
		for (int ivi = 0; ivi < vi; ivi++) {
			for (int ihi = 0; ihi < hi; ihi++) {
				if (progressive) {
					// Progressive: First scan - create a new data unit.
					// Subsequent scans - refine the existing data unit.
					int index = (ymcu * vi + ivi) * compWidth + xmcu * hi + ihi;
					dataUnit = dataUnits[scanComponent][index];
					if (dataUnit is null) {
						dataUnit = new int[64];
						dataUnits[scanComponent][index] = dataUnit;
					}
				} else {
					// Sequential: Clear and reuse the data unit buffer.
					for (int i = 0; i < dataUnit.length; i++) {
						dataUnit[i] = 0;
					}
				}
				if (!progressive || scanHeader.isDCProgressiveScan()) {
					decodeDCCoefficient(dataUnit, scanComponent, first, approxBit);
				}
				if (!progressive) {
					decodeACCoefficients(dataUnit, scanComponent);
				} else {
					if (scanHeader.isACProgressiveScan()) {
						if (first) {
							decodeACFirstCoefficients(dataUnit, scanComponent, start, end, approxBit);
						} else {
							decodeACRefineCoefficients(dataUnit, scanComponent, start, end, approxBit);
						}
					}
					if (loader.hasListeners()) {
						// Dequantization, IDCT, up-sampling and color conversion
						// are done on a copy of the coefficient data in order to
						// display the image incrementally.
						int[] temp = dataUnit;
						dataUnit = new int[64];
						System.arraycopy(temp, 0, dataUnit, 0, 64);
					}
				}
				if (!progressive || (progressive && loader.hasListeners())) {
					dequantize(dataUnit, scanComponent);
					inverseDCT(dataUnit);
					storeData(dataUnit, scanComponent, xmcu, ymcu, hi, ihi, vi, ivi);
				}
			}
		}
	}
}
void decodeScan() {
	if (progressive && !scanHeader.verifyProgressiveScan()) {
		DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
	}
	int nComponentsInScan = scanHeader.getNumberOfImageComponents();
	int mcuRowsInScan = interleavedMcuRows;
	int mcusPerRow = interleavedMcuCols;
	if (nComponentsInScan == 1) {
		// Non-interleaved.
		int scanComponent = 0;
		while (scanHeader.componentParameters[componentIds[scanComponent]] is null) {
			scanComponent++;
		}
		int[] frameComponent = frameComponents[componentIds[scanComponent]];
		int hi = frameComponent[HI];
		int vi = frameComponent[VI];
		int mcuWidth = DCTSIZE * maxH / hi;
		int mcuHeight = DCTSIZE * maxV / vi;
		mcusPerRow = (imageWidth + mcuWidth - 1) / mcuWidth;
		mcuRowsInScan = (imageHeight + mcuHeight - 1) / mcuHeight;
	}
	boolean first = scanHeader.isFirstScan();
	int start = scanHeader.getStartOfSpectralSelection();
	int end = scanHeader.getEndOfSpectralSelection();
	int approxBit = scanHeader.getApproxBitPositionLow();
	restartsToGo = restartInterval;
	nextRestartNumber = 0;
	for (int ymcu = 0; ymcu < mcuRowsInScan; ymcu++) {
		for (int xmcu = 0; xmcu < mcusPerRow; xmcu++) {
			if (restartInterval != 0) {
				if (restartsToGo == 0) processRestartInterval();
				restartsToGo--;
			}
			decodeMCUAtXAndY(xmcu, ymcu, nComponentsInScan, first, start, end, approxBit);
		}
	}
}
int decodeUsingTable(JPEGHuffmanTable huffmanTable) {
	int i = 0;
	int[] maxCodes = huffmanTable.getDhMaxCodes();
	int[] minCodes = huffmanTable.getDhMinCodes();
	int[] valPtrs = huffmanTable.getDhValPtrs();
	int[] huffVals = huffmanTable.getDhValues();
	int code = nextBit();
	while (code > maxCodes[i]) {
		code = code * 2 + nextBit();
		i++;
	}
	int j = valPtrs[i];
	j = j + code - minCodes[i];
	return huffVals[j];
}
void emit(int huffCode, int nBits) {
	if (nBits == 0) {
		DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
	}
	int[] power2m1;
	power2m1 ~= 1; power2m1 ~= 3; power2m1 ~= 7; power2m1 ~= 15; power2m1 ~= 31; power2m1 ~= 63; power2m1 ~= 127; power2m1 ~= 255; power2m1 ~= 511; power2m1 ~= 1023; power2m1 ~= 2047; power2m1 ~= 4095; power2m1 ~= 8191; 
	power2m1 ~= 16383; power2m1 ~= 32767; power2m1 ~= 65535; power2m1 ~= 131125;
	
	int code = (huffCode & power2m1[nBits - 1]) << (24 - nBits - currentBitCount);
	byte[] codeBuffer = new byte[4];
	codeBuffer[0] = cast(byte)(code % 256);
	codeBuffer[1] = cast(byte)((code / 256) % 256);
	codeBuffer[2] = cast(byte)((code / 65536) % 256);
	codeBuffer[3] = cast(byte)((code / 16777216) % 256);
	int abs = nBits - (8 - currentBitCount);
	if (abs < 0) abs = -abs;
	if ((abs / 8) > 0) {
		currentByte += codeBuffer[2];
		emitByte(cast(byte)currentByte);
		emitByte(cast(byte)codeBuffer[1]);
		currentByte = codeBuffer[0];
		currentBitCount += nBits - 16;
	} else {
		currentBitCount += nBits;
		if (currentBitCount >= 8) {
			currentByte += codeBuffer[2];
			emitByte(cast(byte)currentByte);
			currentByte = codeBuffer[1];
			currentBitCount -= 8;
		} else {
			currentByte += codeBuffer[2];
		}
	}
}
void emitByte(byte byteValue) {
	if (bufferCurrentPosition >= 512) {
		resetOutputBuffer();
	}
	dataBuffer[bufferCurrentPosition] = byteValue;
	bufferCurrentPosition++;
	if (byteValue == -1) {
		emitByte(cast(byte)0);
	}
}
void encodeACCoefficients(int[] dataUnit, int iComp) {
	int[] sParams = scanHeader.componentParameters[iComp];
	JPEGHuffmanTable acTable = acHuffmanTables[sParams[AC]];
	int[] ehCodes = acTable.ehCodes;
	byte[] ehSizes = acTable.ehCodeLengths;
	int r = 0;
	int k = 1;
	while (k < 64) {
		k++;
		int acValue = dataUnit[ZigZag8x8[k - 1]];
		if (acValue == 0) {
			if (k == 64) {
				emit(ehCodes[0], ehSizes[0] & 0xFF);
			} else {
				r++;
			}
		} else {
			while (r > 15) {
				emit(ehCodes[0xF0], ehSizes[0xF0] & 0xFF);
				r -= 16;
			}
			if (acValue < 0) {
				int absACValue = acValue;
				if (absACValue < 0) absACValue = -absACValue;
				int nBits = NBitsTable[absACValue];
				int rs = r * 16 + nBits;
				emit(ehCodes[rs], ehSizes[rs] & 0xFF);
				emit(0xFFFFFF - absACValue, nBits);
			} else {
				int nBits = NBitsTable[acValue];
				int rs = r * 16 + nBits;
				emit(ehCodes[rs], ehSizes[rs] & 0xFF);
				emit(acValue, nBits);
			}
			r = 0;
		}
	}
}
void encodeDCCoefficients(int[] dataUnit, int iComp) {
	int[] sParams = scanHeader.componentParameters[iComp];
	JPEGHuffmanTable dcTable = dcHuffmanTables[sParams[DC]];
	int lastDC = precedingDCs[iComp];
	int dcValue = dataUnit[0];
	int diff = dcValue - lastDC;
	precedingDCs[iComp] = dcValue;
	if (diff < 0) {
		int absDiff = 0 - diff;
		int nBits = NBitsTable[absDiff];
		emit(dcTable.ehCodes[nBits], dcTable.ehCodeLengths[nBits]);
		emit(0xFFFFFF - absDiff, nBits);
	} else {
		int nBits = NBitsTable[diff];
		emit(dcTable.ehCodes[nBits], dcTable.ehCodeLengths[nBits]);
		if (nBits != 0) {
			emit(diff, nBits);
		}
	}
}
void encodeMCUAtXAndY(int xmcu, int ymcu) {
	int nComponentsInScan = scanHeader.getNumberOfImageComponents();
	dataUnit = new int[64];
	for (int iComp = 0; iComp < nComponentsInScan; iComp++) {
		int[] frameComponent = frameComponents[componentIds[iComp]];
		int hi = frameComponent[HI];
		int vi = frameComponent[VI];
		for (int ivi = 0; ivi < vi; ivi++) {
			for (int ihi = 0; ihi < hi; ihi++) {
				extractData(dataUnit, iComp, xmcu, ymcu, ihi, ivi);
				forwardDCT(dataUnit);
				quantizeData(dataUnit, iComp);
				encodeDCCoefficients(dataUnit, iComp);
				encodeACCoefficients(dataUnit, iComp);
			}
		}
	}
}
void encodeScan() {
	for (int ymcu = 0; ymcu < interleavedMcuRows; ymcu++) {
		for (int xmcu = 0; xmcu < interleavedMcuCols; xmcu++) {
			encodeMCUAtXAndY(xmcu, ymcu);
		}
	}
	if (currentBitCount != 0) {
		emitByte(cast(byte)currentByte);
	}
	resetOutputBuffer();
}
void expandImageComponents() {
	for (int iComp = 0; iComp < nComponents; iComp++) {
		int[] frameComponent = frameComponents[componentIds[iComp]];
		int hi = frameComponent[HI];
		int vi = frameComponent[VI];
		int upH = maxH / hi;
		int upV = maxV / vi;
		if ((upH * upV) > 1) {
			byte[] component = imageComponents[iComp];
			int compWidth = frameComponent[CW];
			int compHeight = frameComponent[CH];
			int upCompWidth = compWidth * upH;
			int upCompHeight = compHeight * upV;
			DWTRGB[] rgbs;
			rgbs ~= new DWTRGB(0,0,0);
			rgbs ~= new DWTRGB(0x80,0,0);
			rgbs ~= new DWTRGB(0,0x80,0);
			rgbs ~= new DWTRGB(0x80,0x80,0);
			rgbs ~= new DWTRGB(0,0,0x80);
			rgbs ~= new DWTRGB(0x80,0,0x80);
			rgbs ~= new DWTRGB(0,0x80,0x80);
			rgbs ~= new DWTRGB(0xC0,0xC0,0xC0);
			rgbs ~= new DWTRGB(0x80,0x80,0x80);
			rgbs ~= new DWTRGB(0xFF,0,0);
			rgbs ~= new DWTRGB(0,0xFF,0);
			rgbs ~= new DWTRGB(0xFF,0xFF,0);
			rgbs ~= new DWTRGB(0,0,0xFF);
			rgbs ~= new DWTRGB(0xFF,0,0xFF);
			rgbs ~= new DWTRGB(0,0xFF,0xFF);
			rgbs ~= new DWTRGB(0xFF,0xFF,0xFF);
			
			ImageData src = new ImageData(compWidth, compHeight, 8, new PaletteData(rgbs), 4, component);
			ImageData dest = src.scaledTo(upCompWidth, upCompHeight);
			imageComponents[iComp] = dest.data;
		}
	}
}
int extendBy(int diff, int t) {
	if (diff < ExtendTest[t]) {
		return diff + ExtendOffset[t];
	} else {
		return diff;
	}
}
void extractData(int[] dataUnit, int iComp, int xmcu, int ymcu, int ihi, int ivi) {
	byte[] compImage = imageComponents[iComp];
	int[] frameComponent = frameComponents[componentIds[iComp]];
	int hi = frameComponent[HI];
	int vi = frameComponent[VI];
	int compWidth = frameComponent[CW];
	int srcIndex = ((ymcu * vi + ivi) * compWidth * DCTSIZE) + ((xmcu * hi + ihi) * DCTSIZE);
	int destIndex = 0;
	for (int i = 0; i < DCTSIZE; i++) {
		for (int col = 0; col < DCTSIZE; col++) {
			dataUnit[destIndex] = (compImage[srcIndex + col] & 0xFF) - 128;
			destIndex++;
		}
		srcIndex += compWidth;
	}
}
void forwardDCT(int[] dataUnit) {
	for (int row = 0; row < 8; row++) {
		int rIndex = row * DCTSIZE;
		int tmp0 = dataUnit[rIndex] + dataUnit[rIndex + 7];
		int tmp7 = dataUnit[rIndex] - dataUnit[rIndex + 7];
		int tmp1 = dataUnit[rIndex + 1] + dataUnit[rIndex + 6];
		int tmp6 = dataUnit[rIndex + 1] - dataUnit[rIndex + 6];
		int tmp2 = dataUnit[rIndex + 2] + dataUnit[rIndex + 5];
		int tmp5 = dataUnit[rIndex + 2] - dataUnit[rIndex + 5];
		int tmp3 = dataUnit[rIndex + 3] + dataUnit[rIndex + 4];
		int tmp4 = dataUnit[rIndex + 3] - dataUnit[rIndex + 4];

		/**
		 * Even part per LL&M figure 1 --- note that published figure 
		 * is faulty; rotator 'sqrt(2)*c1' should be 'sqrt(2)*c6'.
		 */
		int tmp10 = tmp0 + tmp3;
		int tmp13 = tmp0 - tmp3;
		int tmp11 = tmp1 + tmp2;
		int tmp12 = tmp1 - tmp2;

		dataUnit[rIndex] = (tmp10 + tmp11) * 4;
		dataUnit[rIndex + 4]  = (tmp10 - tmp11) * 4;

		int z1 = (tmp12 + tmp13) * FIX_0_541196100;
		int scaleFactor1 = ExtendTest[11];
		int scaleFactor2 = ExtendTest[12];
		int n = z1 + (tmp13 * FIX_0_765366865) + scaleFactor1;
		dataUnit[rIndex + 2] = n / scaleFactor2;
		if ((n < 0) && (n % scaleFactor2 != 0)) dataUnit[rIndex + 2]--;
		n = z1 + (tmp12 * (0 - FIX_1_847759065)) + scaleFactor1;
 		dataUnit[rIndex + 6] = n / scaleFactor2;
		if ((n < 0) && (n % scaleFactor2 != 0)) dataUnit[rIndex + 6]--;

		/**
		 * Odd part per figure 8 --- note paper omits factor of sqrt(2).
		 * cK represents cos(K*pi/16).
		 * i0..i3 in the paper are tmp4..tmp7 here.
		 */
		z1 = tmp4 + tmp7;
		int z2 = tmp5 + tmp6;
		int z3 = tmp4 + tmp6;
		int z4 = tmp5 + tmp7;
		int z5 = (z3 + z4) * FIX_1_175875602;	// sqrt(2) * c3

		tmp4 = tmp4 * FIX_0_298631336;	// sqrt(2) * (-c1+c3+c5-c7)
		tmp5 = tmp5 * FIX_2_053119869;	// sqrt(2) * ( c1+c3-c5+c7)
		tmp6 = tmp6 * FIX_3_072711026;	// sqrt(2) * ( c1+c3+c5-c7)
		tmp7 = tmp7 * FIX_1_501321110;	// sqrt(2) * ( c1+c3-c5-c7)
		z1 = z1 * (0 - FIX_0_899976223);	// sqrt(2) * (c7-c3)
		z2 = z2 * (0 - FIX_2_562915447);	// sqrt(2) * (-c1-c3)
		z3 = z3 * (0 - FIX_1_961570560);	// sqrt(2) * (-c3-c5)
		z4 = z4 * (0 - FIX_0_390180644);	// sqrt(2) * (c5-c3)

		z3 = z3 + z5;
		z4 = z4 + z5;

		n = tmp4 + z1 + z3 + scaleFactor1;
		dataUnit[rIndex + 7] = n / scaleFactor2;
		if ((n < 0) && (n % scaleFactor2 != 0)) dataUnit[rIndex + 7]--;
		n = tmp5 + z2 + z4 + scaleFactor1;
		dataUnit[rIndex + 5] = n / scaleFactor2;
		if ((n < 0) && (n % scaleFactor2 != 0)) dataUnit[rIndex + 5]--;
		n = tmp6 + z2 + z3 + scaleFactor1;
		dataUnit[rIndex + 3] = n / scaleFactor2;
		if ((n < 0) && (n % scaleFactor2 != 0)) dataUnit[rIndex + 3]--;
		n = tmp7 + z1 + z4 + scaleFactor1;
		dataUnit[rIndex + 1] = n / scaleFactor2;
		if ((n < 0) && (n % scaleFactor2 != 0)) dataUnit[rIndex + 1]--;
	}

	/**
	 * Pass 2: process columns.
	 * Note that we must descale the results by a factor of 8 == 2**3,
	 * and also undo the PASS1_BITS scaling.
	 */
	for (int col = 0; col < 8; col++) {
		int c0 = col;
		int c1 = col + 8;
		int c2 = col + 16;
		int c3 = col + 24;
		int c4 = col + 32;
		int c5 = col + 40;
		int c6 = col + 48;
		int c7 = col + 56;
		int tmp0 = dataUnit[c0] + dataUnit[c7];
		int tmp7 = dataUnit[c0] - dataUnit[c7];
		int tmp1 = dataUnit[c1] + dataUnit[c6];
		int tmp6 = dataUnit[c1] - dataUnit[c6];
		int tmp2 = dataUnit[c2] + dataUnit[c5];
		int tmp5 = dataUnit[c2] - dataUnit[c5];
		int tmp3 = dataUnit[c3] + dataUnit[c4];
		int tmp4 = dataUnit[c3] - dataUnit[c4];

		/**
		 * Even part per LL&M figure 1 --- note that published figure 
		 * is faulty; rotator 'sqrt(2)*c1' should be 'sqrt(2)*c6'.
		 */
		int tmp10 = tmp0 + tmp3;
		int tmp13 = tmp0 - tmp3;
		int tmp11 = tmp1 + tmp2;
		int tmp12 = tmp1 - tmp2;

		int scaleFactor1 = ExtendTest[5];
		int scaleFactor2 = ExtendTest[6];
		int n = tmp10 + tmp11 + scaleFactor1;
		dataUnit[c0] = n / scaleFactor2;
		if ((n < 0) && (n % scaleFactor2 != 0)) dataUnit[c0]--;
		n = tmp10 - tmp11 + scaleFactor1;
		dataUnit[c4] = n / scaleFactor2;
		if ((n < 0) && (n % scaleFactor2 != 0)) dataUnit[c4]--;

		int z1 = (tmp12 + tmp13) * FIX_0_541196100;
		scaleFactor1 = ExtendTest[18];
		scaleFactor2 = ExtendTest[19];
		n = z1 + (tmp13 * FIX_0_765366865) + scaleFactor1;
		dataUnit[c2] = n / scaleFactor2;
		if ((n < 0) && (n % scaleFactor2 != 0)) dataUnit[c2]--;
		n = z1 + (tmp12 * (0 - FIX_1_847759065)) + scaleFactor1;
		dataUnit[c6] = n / scaleFactor2;
		if ((n < 0) && (n % scaleFactor2 != 0)) dataUnit[c6]--;

		/**
		 * Odd part per figure 8 --- note paper omits factor of sqrt(2).
		 * cK represents cos(K*pi/16).
		 * i0..i3 in the paper are tmp4..tmp7 here.
		 */
		z1 = tmp4 + tmp7;
		int z2 = tmp5 + tmp6;
		int z3 = tmp4 + tmp6;
		int z4 = tmp5 + tmp7;
		int z5 = (z3 + z4) * FIX_1_175875602;	// sqrt(2) * c3

		tmp4 = tmp4 * FIX_0_298631336;	// sqrt(2) * (-c1+c3+c5-c7)
		tmp5 = tmp5 * FIX_2_053119869;	// sqrt(2) * ( c1+c3-c5+c7)
		tmp6 = tmp6 * FIX_3_072711026;	// sqrt(2) * ( c1+c3+c5-c7)
		tmp7 = tmp7 * FIX_1_501321110;	// sqrt(2) * ( c1+c3-c5-c7)
		z1 = z1 * (0 - FIX_0_899976223);	// sqrt(2) * (c7-c3)
		z2 = z2 * (0 - FIX_2_562915447);	// sqrt(2) * (-c1-c3)
		z3 = z3 * (0 - FIX_1_961570560);	// sqrt(2) * (-c3-c5)
		z4 = z4 * (0 - FIX_0_390180644);	// sqrt(2) * (c5-c3)

		z3 = z3 + z5;
		z4 = z4 + z5;

		n = tmp4 + z1 + z3 + scaleFactor1;
		dataUnit[c7] = n / scaleFactor2;
		if ((n < 0) && (n % scaleFactor2 != 0)) dataUnit[c7]--;
		n = tmp5 + z2 + z4 + scaleFactor1;
		dataUnit[c5] = n / scaleFactor2;
		if ((n < 0) && (n % scaleFactor2 != 0)) dataUnit[c5]--;
		n = tmp6 + z2 + z3 + scaleFactor1;
		dataUnit[c3] = n / scaleFactor2;
		if ((n < 0) && (n % scaleFactor2 != 0)) dataUnit[c3]--;
		n = tmp7 + z1 + z4 + scaleFactor1;
		dataUnit[c1] = n / scaleFactor2;
		if ((n < 0) && (n % scaleFactor2 != 0)) dataUnit[c1]--;
	}
}
void getAPP0() {
	JPEGAppn appn = new JPEGAppn(inputStream);
	if (!appn.verify()) {
		DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
	}
}
void getCOM() {
	new JPEGComment(inputStream);
}
void getDAC() {
	JPEGArithmeticConditioningTable dac = new JPEGArithmeticConditioningTable(inputStream);
	arithmeticTables = dac;
}
void getDHT() {
	JPEGHuffmanTable dht = new JPEGHuffmanTable(inputStream);
	if (!dht.verify()) {
		DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
	}
	if (acHuffmanTables is null) {
		acHuffmanTables = new JPEGHuffmanTable[4];
	}
	if (dcHuffmanTables is null) {
		dcHuffmanTables = new JPEGHuffmanTable[4];
	}
	JPEGHuffmanTable[] dhtTables = dht.getAllTables();
	for (int i = 0; i < dhtTables.length; i++) {
		JPEGHuffmanTable dhtTable = dhtTables[i];
		if (dhtTable.getTableClass() == 0) {
			dcHuffmanTables[dhtTable.getTableIdentifier()] = dhtTable;
		} else {
			acHuffmanTables[dhtTable.getTableIdentifier()] = dhtTable;
		}
	}
}
void getDNL() {
	new JPEGRestartInterval(inputStream);
}
void getDQT() {
	JPEGQuantizationTable dqt = new JPEGQuantizationTable(inputStream);
	int[][] currentTables = quantizationTables;
	if (currentTables is null) {
		currentTables = new int[][4];
	}
	int[] dqtTablesKeys = dqt.getQuantizationTablesKeys();
	int[][] dqtTablesValues = dqt.getQuantizationTablesValues();
	for (int i = 0; i < dqtTablesKeys.length; i++) {
		int index = dqtTablesKeys[i];
		currentTables[index] = dqtTablesValues[i];
	}
	quantizationTables = currentTables;
}
void getDRI() {
	JPEGRestartInterval dri = new JPEGRestartInterval(inputStream);
	if (!dri.verify()) {
		DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
	}
	restartInterval = dri.getRestartInterval();
}
static void initialize() {
	initializeRGBYCbCrTables();
	initializeYCbCrRGBTables();
	initializeBitCountTable();
}
static void initializeBitCountTable() {
	int nBits = 1;
	int power2 = 2;
	NBitsTable = new int[2048];
	NBitsTable[0] = 0;
	for (int i = 1; i < NBitsTable.length; i++) {
		if (!(i < power2)) {
			nBits++;
			power2 *= 2;
		}
		NBitsTable[i] = nBits;
	}
}
static void initializeRGBYCbCrTables() {
	RYTable = new int[256];
	GYTable = new int[256];
	BYTable = new int[256];
	RCbTable = new int[256];
	GCbTable = new int[256];
	BCbTable = new int[256];
	RCrTable = BCbTable;
	GCrTable = new int[256];
	BCrTable = new int[256];
	for (int i = 0; i < 256; i++) {
		RYTable[i] = i * 19595;
		GYTable[i] = i * 38470;
		BYTable[i] = i * 7471 + 32768;
		RCbTable[i] = i * -11059;
		GCbTable[i] = i * -21709;
		BCbTable[i] = i * 32768 + 8388608;
		GCrTable[i] = i * -27439;
		BCrTable[i] = i * -5329;
	}
}
static void initializeYCbCrRGBTables() {
	CrRTable = new int[256];
	CbBTable = new int[256];
	CrGTable = new int[256];
	CbGTable = new int[256];
	for (int i = 0; i < 256; i++) {
		int x2 = 2 * i - 255;
		CrRTable[i] = (45941 * x2 + 32768) / 65536;
		CbBTable[i] = (58065 * x2 + 32768) / 65536;
		CrGTable[i] = -23401 * x2;
		CbGTable[i] = -11277 * x2 + 32768;
	}
}
void inverseDCT(int[] dataUnit) {
	for (int row = 0; row < 8; row++) {
		int rIndex = row * DCTSIZE;
		/**
		 * Due to quantization, we will usually find that many of the input
		 * coefficients are zero, especially the AC terms.  We can exploit this
		 * by short-circuiting the IDCT calculation for any row in which all
		 * the AC terms are zero.  In that case each output is equal to the
		 * DC coefficient (with scale factor as needed).
		 * With typical images and quantization tables, half or more of the
		 * row DCT calculations can be simplified this way.
		 */
		if (isZeroInRow(dataUnit, rIndex)) {
			int dcVal = dataUnit[rIndex] * 4;
			for (int i = rIndex; i < rIndex + 8; i++) {
				dataUnit[i] = dcVal;
			}
		} else {
			/**
			 * Even part: reverse the even part of the forward DCT.
			 * The rotator is sqrt(2)*c(-6).
			 */
			int z2 = dataUnit[rIndex + 2];
			int z3 = dataUnit[rIndex + 6];
			int z1 = (z2 + z3) * FIX_0_541196100;
			int tmp2 = z1 + (z3 * (0 - FIX_1_847759065));
			int tmp3 = z1 + (z2 * FIX_0_765366865);
			int tmp0 = (dataUnit[rIndex] + dataUnit[rIndex + 4]) * 8192;
			int tmp1 = (dataUnit[rIndex] - dataUnit[rIndex + 4]) * 8192;
			int tmp10 = tmp0 + tmp3;
			int tmp13 = tmp0 - tmp3;
			int tmp11 = tmp1 + tmp2;
			int tmp12 = tmp1 - tmp2;
			/**
			 * Odd part per figure 8; the matrix is unitary and hence its
			 * transpose is its inverse. i0..i3 are y7,y5,y3,y1 respectively.
			 */
			tmp0 = dataUnit[rIndex + 7];
			tmp1 = dataUnit[rIndex + 5];
			tmp2 = dataUnit[rIndex + 3];
			tmp3 = dataUnit[rIndex + 1];
			z1 = tmp0 + tmp3;
			z2 = tmp1 + tmp2;
			z3 = tmp0 + tmp2;
			int z4 = tmp1 + tmp3;
			int z5 = (z3 + z4)* FIX_1_175875602; /* sqrt(2) * c3 */
			  
			tmp0 = tmp0 * FIX_0_298631336;		/* sqrt(2) * (-c1+c3+c5-c7) */
			tmp1 = tmp1 * FIX_2_053119869;		/* sqrt(2) * ( c1+c3-c5+c7) */
			tmp2 = tmp2 * FIX_3_072711026;		/* sqrt(2) * ( c1+c3+c5-c7) */
			tmp3 = tmp3 * FIX_1_501321110;		/* sqrt(2) * ( c1+c3-c5-c7) */
			z1 = z1 * (0 - FIX_0_899976223);	/* sqrt(2) * (c7-c3) */
			z2 = z2 * (0 - FIX_2_562915447);	/* sqrt(2) * (-c1-c3) */
			z3 = z3 * (0 - FIX_1_961570560);	/* sqrt(2) * (-c3-c5) */
			z4 = z4 * (0 - FIX_0_390180644);	/* sqrt(2) * (c5-c3) */

			z3 = z3 + z5;
			z4 = z4 + z5;
			tmp0 = tmp0 + z1 + z3;
			tmp1 = tmp1 + z2 + z4;
			tmp2 = tmp2 + z2 + z3;
			tmp3 = tmp3 + z1 + z4;

			int descaleFactor1 = ExtendTest[11];
			int descaleFactor2 = ExtendTest[12];
			dataUnit[rIndex] = (tmp10 + tmp3 + descaleFactor1) / descaleFactor2;
			dataUnit[rIndex + 7] = (tmp10 - tmp3 + descaleFactor1) / descaleFactor2;
			dataUnit[rIndex + 1] = (tmp11 + tmp2 + descaleFactor1) / descaleFactor2;
			dataUnit[rIndex + 6] = (tmp11 - tmp2 + descaleFactor1) / descaleFactor2;
			dataUnit[rIndex + 2] = (tmp12 + tmp1 + descaleFactor1) / descaleFactor2;
			dataUnit[rIndex + 5] = (tmp12 - tmp1 + descaleFactor1) / descaleFactor2;
			dataUnit[rIndex + 3] = (tmp13 + tmp0 + descaleFactor1) / descaleFactor2;
			dataUnit[rIndex + 4] = (tmp13 - tmp0 + descaleFactor1) / descaleFactor2;
		 }
	}
	/**
	 * Pass 2: process columns.
	 * Note that we must descale the results by a factor of 8 == 2**3,
	 * and also undo the PASS1_BITS scaling.
	 */
	for (int col = 0; col < 8; col++) {
		int c0 = col;
		int c1 = col + 8;
		int c2 = col + 16;
		int c3 = col + 24;
		int c4 = col + 32;
		int c5 = col + 40;
		int c6 = col + 48;
		int c7 = col + 56;
		if (isZeroInColumn(dataUnit, col)) {
			int dcVal = (dataUnit[c0] + 16) / 32;
			dataUnit[c0] = dcVal;
			dataUnit[c1] = dcVal;
			dataUnit[c2] = dcVal;
			dataUnit[c3] = dcVal;
			dataUnit[c4] = dcVal;
			dataUnit[c5] = dcVal;
			dataUnit[c6] = dcVal;
			dataUnit[c7] = dcVal;
		} else {
			/**
			 * Even part: reverse the even part of the forward DCT.
			 * The rotator is sqrt(2)*c(-6).
			 */
			int z2 = dataUnit[c2];
			int z3 = dataUnit[c6];
			int z1 = (z2 + z3) * FIX_0_541196100;
			int tmp2 = z1 + (z3 * (0 - FIX_1_847759065));
			int tmp3 = z1 + (z2 * FIX_0_765366865);
			int tmp0 = (dataUnit[c0] + dataUnit[c4]) * 8192;
			int tmp1 = (dataUnit[c0] - dataUnit[c4]) * 8192;
			int tmp10 = tmp0 + tmp3;
			int tmp13 = tmp0 - tmp3;
			int tmp11 = tmp1 + tmp2;
			int tmp12 = tmp1 - tmp2;
			/**
			 * Odd part per figure 8; the matrix is unitary and hence its
			 * transpose is its inverse. i0..i3 are y7,y5,y3,y1 respectively.
			 */
			tmp0 = dataUnit[c7];
			tmp1 = dataUnit[c5];
			tmp2 = dataUnit[c3];
			tmp3 = dataUnit[c1];
			z1 = tmp0 + tmp3;
			z2 = tmp1 + tmp2;
			z3 = tmp0 + tmp2;
			int z4 = tmp1 + tmp3;
			int z5 = (z3 + z4) * FIX_1_175875602;	/* sqrt(2) * c3 */
			
			tmp0 = tmp0 * FIX_0_298631336;		/* sqrt(2) * (-c1+c3+c5-c7) */
			tmp1 = tmp1 * FIX_2_053119869;		/* sqrt(2) * ( c1+c3-c5+c7) */
			tmp2 = tmp2 * FIX_3_072711026;		/* sqrt(2) * ( c1+c3+c5-c7) */
			tmp3 = tmp3 * FIX_1_501321110;		/* sqrt(2) * ( c1+c3-c5-c7) */
			z1 = z1 * (0 - FIX_0_899976223);	/* sqrt(2) * (c7-c3) */
			z2 = z2 * (0 - FIX_2_562915447);	/* sqrt(2) * (-c1-c3) */
			z3 = z3 * (0 - FIX_1_961570560);	/* sqrt(2) * (-c3-c5) */
			z4 = z4 * (0 - FIX_0_390180644);	/* sqrt(2) * (c5-c3) */
			
			z3 = z3 + z5;
			z4 = z4 + z5;
			
			tmp0 = tmp0 + z1 + z3;
			tmp1 = tmp1 + z2 + z4;
			tmp2 = tmp2 + z2 + z3;
			tmp3 = tmp3 + z1 + z4;

			/* Final output stage: inputs are tmp10..tmp13, tmp0..tmp3 */
			int descaleFactor1 = ExtendTest[18];
			int descaleFactor2 = ExtendTest[19];
			dataUnit[c0] = (tmp10 + tmp3 + descaleFactor1) / descaleFactor2;
			dataUnit[c7] = (tmp10 - tmp3 + descaleFactor1) / descaleFactor2;
			dataUnit[c1] = (tmp11 + tmp2 + descaleFactor1) / descaleFactor2;
			dataUnit[c6] = (tmp11 - tmp2 + descaleFactor1) / descaleFactor2;
			dataUnit[c2] = (tmp12 + tmp1 + descaleFactor1) / descaleFactor2;
			dataUnit[c5] = (tmp12 - tmp1 + descaleFactor1) / descaleFactor2;
			dataUnit[c3] = (tmp13 + tmp0 + descaleFactor1) / descaleFactor2;
			dataUnit[c4] = (tmp13 - tmp0 + descaleFactor1) / descaleFactor2;
		}
	}
}
boolean isFileFormat(LEDataInputStream stream) {
	try {
		JPEGStartOfImage soi = new JPEGStartOfImage(stream);
		stream.unread(soi.reference);
		return soi.verify();  // we no longer check for appN
	} catch (Exception e) {
		return false;
	}
}
boolean isZeroInColumn(int[] dataUnit, int col) {
	return (dataUnit[col + 8] + dataUnit[col + 16] +
		dataUnit[col + 24] + dataUnit[col + 32] +
		dataUnit[col + 40] + dataUnit[col + 48] +
		dataUnit[col + 56]) == 0;
}
boolean isZeroInRow(int[] dataUnit, int rIndex) {
	return (dataUnit[rIndex + 1] + dataUnit[rIndex + 2] +
		dataUnit[rIndex + 3] + dataUnit[rIndex + 4] +
		dataUnit[rIndex + 5] + dataUnit[rIndex + 6] +
		dataUnit[rIndex + 7]) == 0;
}
ImageData[] loadFromByteStream() {
	JPEGStartOfImage soi = new JPEGStartOfImage(inputStream);
	if (!soi.verify()) {
		DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
		return null;
	}
	restartInterval = 0;

	/* Process the tables preceding the frame header. */
	processTables();
	
	/* Start of Frame. */
	frameHeader = new JPEGFrameHeader(inputStream);
	if (!frameHeader.verify()) DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
	imageWidth = frameHeader.getSamplesPerLine();
	imageHeight = frameHeader.getNumberOfLines();
	maxH = frameHeader.getMaxHFactor();
	maxV = frameHeader.getMaxVFactor();
	int mcuWidth = maxH * DCTSIZE;
	int mcuHeight = maxV * DCTSIZE;
	interleavedMcuCols = (imageWidth + mcuWidth - 1) / mcuWidth;
	interleavedMcuRows = (imageHeight + mcuHeight - 1) / mcuHeight;
	progressive = frameHeader.isProgressive();
	samplePrecision = frameHeader.getSamplePrecision();
	nComponents = frameHeader.getNumberOfImageComponents();
	frameComponents = frameHeader.componentParameters;
	componentIds = frameHeader.componentIdentifiers;

	imageComponents = new byte[][nComponents];
	if (progressive) {
		// Progressive jpeg: need to keep all of the data units.
		//TODO:
//		dataUnits = new int[nComponents][][];
		dataUnits = new int[][][nComponents];
	} else {
		// Sequential jpeg: only need one data unit.
		dataUnit = new int[8 * 8];
	}
	for (int i = 0; i < nComponents; i++) {
		int[] frameComponent = frameComponents[componentIds[i]];
		int bufferSize = frameComponent[CW] * frameComponent[CH];
		imageComponents[i] = new byte[bufferSize];
		if (progressive) {
			dataUnits[i] = new int[][bufferSize];
		}
	}

	/* Process the tables preceding the scan header. */
	processTables();
	
	/* Start of Scan. */
	scanHeader = new JPEGScanHeader(inputStream);
	if (!scanHeader.verify()) DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
	
	/* Process scan(s) and further tables until EOI. */
	int progressiveScanCount = 0;
	boolean done = false;
	while(!done) {
		resetInputBuffer();
		precedingDCs = new int[4];
		decodeScan();
		if (progressive && loader.hasListeners()) {
			ImageData imageData = createImageData();
			loader.notifyListeners(new ImageLoaderEvent(loader, imageData, progressiveScanCount, false));
			progressiveScanCount++;
		}

		/* Unread any buffered data before looking for tables again. */
		int delta = 512 - bufferCurrentPosition - 1;
		if (delta > 0) {
			byte[] unreadBuffer = new byte[delta];
			System.arraycopy(dataBuffer, bufferCurrentPosition + 1, unreadBuffer, 0, delta);
			try {
				inputStream.unread(unreadBuffer);
			} catch (Exception e) {
				DWT.error(__FILE__, __LINE__,  e);
			}
		}
		
		/* Process the tables preceding the next scan header. */
		JPEGSegment jpegSegment = processTables();
		if (jpegSegment is null || jpegSegment.getSegmentMarker() == EOI) {
			done = true;
		} else {
			scanHeader = new JPEGScanHeader(inputStream);
			if (!scanHeader.verify()) DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
		}
	}
	
	if (progressive) {
		for (int ymcu = 0; ymcu < interleavedMcuRows; ymcu++) {
			for (int xmcu = 0; xmcu < interleavedMcuCols; xmcu++) {
				for (int iComp = 0; iComp < nComponents; iComp++) {
					int[] frameComponent = frameComponents[componentIds[iComp]];
					int hi = frameComponent[HI];
					int vi = frameComponent[VI];
					int compWidth = frameComponent[CW];
					for (int ivi = 0; ivi < vi; ivi++) {
						for (int ihi = 0; ihi < hi; ihi++) {
							int index = (ymcu * vi + ivi) * compWidth + xmcu * hi + ihi;
							dataUnit = dataUnits[iComp][index];
							dequantize(dataUnit, iComp);
							inverseDCT(dataUnit);
							storeData(dataUnit, iComp, xmcu, ymcu, hi, ihi, vi, ivi);
						}
					}
				}
			}
		}
	}
	ImageData imageData = createImageData();
	if (progressive && loader.hasListeners()) {
		loader.notifyListeners(new ImageLoaderEvent(loader, imageData, progressiveScanCount, true));
	}
	ImageData[] ImageDataArr;
	ImageDataArr ~= imageData;
	return ImageDataArr;
}
ImageData createImageData() {
	return ImageData.internal_new(
		imageWidth,
		imageHeight, 
		nComponents * samplePrecision,
		setUpPalette(),
		nComponents == 1 ? 4 : 1,
		decodeImageComponents(),
		0,
		null,
		null,
		-1,
		-1,
		DWT.IMAGE_JPEG,
		0,
		0,
		0,
		0);
}
int nextBit() {
	if (currentBitCount != 0) {
		currentBitCount--;
		currentByte *= 2;
		if (currentByte > 255) {
			currentByte -= 256;
			return 1;
		} else {
			return 0;
		}
	}
	bufferCurrentPosition++;
	if (bufferCurrentPosition >= 512) {
		resetInputBuffer();
		bufferCurrentPosition = 0;
	}
	currentByte = dataBuffer[bufferCurrentPosition] & 0xFF;
	currentBitCount = 8;
	byte nextByte;
	if (bufferCurrentPosition == 511) {
		resetInputBuffer();
		currentBitCount = 8;
		nextByte = dataBuffer[0];
	} else {
		nextByte = dataBuffer[bufferCurrentPosition + 1];
	}
	if (currentByte == 0xFF) {
		if (nextByte == 0) {
			bufferCurrentPosition ++;
			currentBitCount--;
			currentByte *= 2;
			if (currentByte > 255) {
				currentByte -= 256;
				return 1;
			} else {
				return 0;
			}
		} else {
			if ((nextByte & 0xFF) + 0xFF00 == DNL) {
				getDNL();
				return 0;
			} else {
				DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
				return 0;
			}
		}
	} else {
		currentBitCount--;
		currentByte *= 2;
		if (currentByte > 255) {
			currentByte -= 256;
			return 1;
		} else {
			return 0;
		}
	}
}
void processRestartInterval() {
	do {
		bufferCurrentPosition++;
		if (bufferCurrentPosition > 511) {
			resetInputBuffer();
			bufferCurrentPosition = 0;
		}
		currentByte = dataBuffer[bufferCurrentPosition] & 0xFF;
	} while (currentByte != 0xFF);
	while (currentByte == 0xFF) {
		bufferCurrentPosition++;
		if (bufferCurrentPosition > 511) {
			resetInputBuffer();
			bufferCurrentPosition = 0;
		}
		currentByte = dataBuffer[bufferCurrentPosition] & 0xFF;
	}
	if (currentByte != ((RST0 + nextRestartNumber) % 256)) {
		DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
	}
	bufferCurrentPosition++;
	if (bufferCurrentPosition > 511) {
		resetInputBuffer();
		bufferCurrentPosition = 0;
	}
	currentByte = dataBuffer[bufferCurrentPosition] & 0xFF;
	currentBitCount = 8;
	restartsToGo = restartInterval;
	nextRestartNumber = (nextRestartNumber + 1) % 8;
	precedingDCs = new int[4];
	eobrun = 0;
}
/* Process all markers until a frame header, scan header, or EOI is found. */
JPEGSegment processTables() {
	while (true) {
		JPEGSegment jpegSegment = seekUnspecifiedMarker(inputStream);
		if (jpegSegment is null) return null;
		JPEGFrameHeader sof = new JPEGFrameHeader(jpegSegment.reference);
		if (sof.verify()) {
			return jpegSegment;
		}
		int marker = jpegSegment.getSegmentMarker();
		switch (marker) {
			case SOI: // there should only be one SOI per file
				DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
			case EOI:
			case SOS:
				return jpegSegment;
			case DQT:
				getDQT();
				break;
			case DHT:
				getDHT();
				break;
			case DAC:
				getDAC();
				break;
			case DRI:
				getDRI();
				break;
			case APP0:
				getAPP0();
				break;
			case COM:
				getCOM();
				break;
			default:
				skipSegmentFrom(inputStream);
			
		}
	}
	return null;
}
void quantizeData(int[] dataUnit, int iComp) {
	int[] qTable = quantizationTables[frameComponents[componentIds[iComp]][TQI]];
	for (int i = 0; i < dataUnit.length; i++) {
		int zzIndex = ZigZag8x8[i];
		int data = dataUnit[zzIndex];
		int absData = data < 0 ? 0 - data : data;
		int qValue = qTable[i];
		int q2 = qValue / 2;
		absData += q2;
		if (absData < qValue) {
			dataUnit[zzIndex] = 0;
		} else {
			absData /= qValue;
			if (data >= 0) {
				dataUnit[zzIndex] = absData;
			} else {
				dataUnit[zzIndex] = 0 - absData;
			}
		}
	}
}
int receive(int nBits) {
	int v = 0;
	for (int i = 0; i < nBits; i++) {
		v = v * 2 + nextBit();
	}
	return v;
}
void resetInputBuffer() {
	if (dataBuffer is null) {
		dataBuffer = new byte[512];
	}
	try {
		inputStream.read(dataBuffer);
	} catch (StreamException e) {
		DWT.error(__FILE__, __LINE__,  e);
	}
	currentBitCount = 0;
	bufferCurrentPosition = -1;
}
void resetOutputBuffer() {
	if (dataBuffer is null) {
		dataBuffer = new byte[512];
	} else {
		try {
			outputStream.write(dataBuffer, 0, bufferCurrentPosition);
		} catch (StreamException e) {
			DWT.error(__FILE__, __LINE__,  e);
		}
	}
	bufferCurrentPosition = 0;
}
static JPEGSegment seekUnspecifiedMarker(LEDataInputStream byteStream) {
	byte[] byteArray = new byte[2];
	try {
		while (true) {
			if (byteStream.readBytes(byteArray, 0, 1) != 1) return null;
			if (byteArray[0] == cast(byte) 0xFF) {
				if (byteStream.readBytes(byteArray, 1, 1) != 1) return null;
				if (byteArray[1] != cast(byte) 0xFF && byteArray[1] != 0) {
					byteStream.unread(byteArray);
					return new JPEGSegment(byteArray);
				}
			}
		}
	} catch (StreamException e) {
		DWT.error(__FILE__, __LINE__,  e);
	}
	return null;
}
PaletteData setUpPalette() {
	if (nComponents == 1) {
		DWTRGB[] entries = new DWTRGB[256];
		for (int i = 0; i < 256; i++) {
			entries[i] = new DWTRGB(i, i, i);
		}
		return new PaletteData(entries);
	}
	return new PaletteData(0xFF, 0xFF00, 0xFF0000);
}
static void skipSegmentFrom(LEDataInputStream byteStream) {
	try {
		byte[] byteArray = new byte[4];
		JPEGSegment jpegSegment = new JPEGSegment(byteArray);
	
		if (byteStream.read(byteArray) != byteArray.length) {
			DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
		}
		if (!(byteArray[0] == -1 && byteArray[1] != 0 && byteArray[1] != -1)) {
			DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
		}
		int delta = jpegSegment.getSegmentLength() - 2;
		byteStream.skip(delta);
	} catch (Exception e) {
		DWT.error(__FILE__, __LINE__,  e);
	}
}
void storeData(int[] dataUnit, int iComp, int xmcu, int ymcu, int hi, int ihi, int vi, int ivi) {
	byte[] compImage = imageComponents[iComp];
	int[] frameComponent = frameComponents[componentIds[iComp]];
	int compWidth = frameComponent[CW];
	int destIndex = ((ymcu * vi + ivi) * compWidth * DCTSIZE) + ((xmcu * hi + ihi) * DCTSIZE);
	int srcIndex = 0;
	for (int i = 0; i < DCTSIZE; i++) {
		for (int col = 0; col < DCTSIZE; col++) {
			int x = dataUnit[srcIndex] + 128;
			if (x < 0) {
				x = 0;
			} else {
				if (x > 255) x = 255;
			}
			compImage[destIndex + col] = cast(byte)x;
			srcIndex++;
		}
		destIndex += compWidth;
	}
}
void unloadIntoByteStream(ImageData image) {
	if (!(new JPEGStartOfImage()).writeToStream(outputStream)) {
		DWT.error(__FILE__, __LINE__, DWT.ERROR_IO);
	}
	byte[] byteArr;
	byteArr ~= cast(byte)0xFF;
	byteArr ~= cast(byte)0xE0;
	byteArr ~= 0;
	byteArr ~= 0x10;
	byteArr ~= 0x4A;
	byteArr ~= 0x46;
	byteArr ~= 0x49;
	byteArr ~= 0x46;
	byteArr ~= 0;
	byteArr ~= 1;
	byteArr ~= 1;
	byteArr ~= 0;
	byteArr ~= 0;
	byteArr ~= 1;
	byteArr ~= 0;
	byteArr ~= 1;
	byteArr ~= 0;
	byteArr ~= 0;
	JPEGAppn appn = new JPEGAppn(byteArr);
	if (!appn.writeToStream(outputStream)) {
		DWT.error(__FILE__, __LINE__, DWT.ERROR_IO);
	}
	quantizationTables = new int[][4];
	JPEGQuantizationTable chromDQT = JPEGQuantizationTable.defaultChrominanceTable();
	chromDQT.scaleBy(encoderQFactor);
	int[] jpegDQTKeys = chromDQT.getQuantizationTablesKeys();
	int[][] jpegDQTValues = chromDQT.getQuantizationTablesValues();
	for (int i = 0; i < jpegDQTKeys.length; i++) {
		quantizationTables[jpegDQTKeys[i]] = jpegDQTValues[i];
	}
	JPEGQuantizationTable lumDQT = JPEGQuantizationTable.defaultLuminanceTable();
	lumDQT.scaleBy(encoderQFactor);
	jpegDQTKeys = lumDQT.getQuantizationTablesKeys();
	jpegDQTValues = lumDQT.getQuantizationTablesValues();
	for (int i = 0; i < jpegDQTKeys.length; i++) {
		quantizationTables[jpegDQTKeys[i]] = jpegDQTValues[i];
	}
	if (!lumDQT.writeToStream(outputStream)) {
		DWT.error(__FILE__, __LINE__, DWT.ERROR_IO);
	}
	if (!chromDQT.writeToStream(outputStream)) {
		DWT.error(__FILE__, __LINE__, DWT.ERROR_IO);
	}
	int frameLength, scanLength, precision;
	int[][] frameParams, scanParams;
	if (image.depth == 1) {
		frameLength = 11;
		frameParams = new int[][1];
		int[] intArr;
		intArr ~= 1;
		intArr ~= 1;
		intArr ~= 1;
		intArr ~= 0;
		intArr ~= 0;
		frameParams[0] = intArr;
		scanParams = new int[][1];
		int[] intArr0;
		intArr0 ~= 0;
		intArr0 ~= 0;
		scanParams[0] = intArr0;
		scanLength = 8;
		nComponents = 1;
		precision = 1;
	} else {
		frameLength = 17;
		frameParams = new int[][3];
		int[] intArr1;
		intArr1 ~= 0;
		intArr1 ~= 2;
		intArr1 ~= 2;
		intArr1 ~= 0;
		intArr1 ~= 0;
		frameParams[0] = intArr1;
		int[] intArr2;
		intArr2 ~= 1;
		intArr2 ~= 1;
		intArr2 ~= 1;
		intArr2 ~= 0;
		intArr2 ~= 0;
		frameParams[1] = intArr2;
		int[] intArr3;
		intArr3 ~= 1;
		intArr3 ~= 1;
		intArr3 ~= 1;
		intArr3 ~= 0;
		intArr3 ~= 0;
		frameParams[2] = intArr3;
		scanParams = new int[][3];
		int[] intArr4;
		intArr4 ~= 0;
		intArr4 ~= 0;
		scanParams[0] = intArr4;
		int[] intArr5;
		intArr5 ~= 1;
		intArr5 ~= 1;
		scanParams[1] = intArr5;
		int[] intArr6;
		intArr6 ~= 1;
		intArr6 ~= 1;
		scanParams[2] = intArr6;
		scanLength = 12;
		nComponents = 3;
		precision = 8;
	}
	imageWidth = image.width;
	imageHeight = image.height;
	frameHeader = new JPEGFrameHeader(new byte[19]);
	frameHeader.setSegmentMarker(SOF0);
	frameHeader.setSegmentLength(frameLength);
	frameHeader.setSamplePrecision(precision);
	frameHeader.setSamplesPerLine(imageWidth);
	frameHeader.setNumberOfLines(imageHeight);
	frameHeader.setNumberOfImageComponents(nComponents);
	frameHeader.componentParameters = frameParams;
	int[] intArr7;
	intArr7 ~= 0;
	intArr7 ~= 1;
	intArr7 ~= 2;
	frameHeader.componentIdentifiers = intArr7;
	frameHeader.initializeContents();
	if (!frameHeader.writeToStream(outputStream)) {
		DWT.error(__FILE__, __LINE__, DWT.ERROR_IO);
	}
	frameComponents = frameParams;
	componentIds = frameHeader.componentIdentifiers;
	maxH = frameHeader.getMaxHFactor();
	maxV = frameHeader.getMaxVFactor();
	int mcuWidth = maxH * DCTSIZE;
	int mcuHeight = maxV * DCTSIZE;
	interleavedMcuCols = (imageWidth + mcuWidth - 1) / mcuWidth;
	interleavedMcuRows = (imageHeight + mcuHeight - 1) / mcuHeight;
	acHuffmanTables = new JPEGHuffmanTable[4];
	dcHuffmanTables = new JPEGHuffmanTable[4];
	JPEGHuffmanTable[] dhtTables;
	dhtTables ~= JPEGHuffmanTable.getDefaultDCLuminanceTable();
	dhtTables ~= JPEGHuffmanTable.getDefaultDCChrominanceTable();
	dhtTables ~= JPEGHuffmanTable.getDefaultACLuminanceTable();
	dhtTables ~= JPEGHuffmanTable.getDefaultACChrominanceTable();
	for (int i = 0; i < dhtTables.length; i++) {
		JPEGHuffmanTable dhtTable = dhtTables[i];
		if (!dhtTable.writeToStream(outputStream)) {
			DWT.error(__FILE__, __LINE__, DWT.ERROR_IO);
		}
		JPEGHuffmanTable[] allTables = dhtTable.getAllTables();
		for (int j = 0; j < allTables.length; j++) {
			JPEGHuffmanTable huffmanTable = allTables[j];
			if (huffmanTable.getTableClass() == 0) {
				dcHuffmanTables[huffmanTable.getTableIdentifier()] = huffmanTable;
			} else {
				acHuffmanTables[huffmanTable.getTableIdentifier()] = huffmanTable;
			}
		}
	}
	precedingDCs = new int[4];
	scanHeader = new JPEGScanHeader(new byte[14]);
	scanHeader.setSegmentMarker(SOS);
	scanHeader.setSegmentLength(scanLength);
	scanHeader.setNumberOfImageComponents(nComponents);
	scanHeader.setStartOfSpectralSelection(0);
	scanHeader.setEndOfSpectralSelection(63);
	scanHeader.componentParameters = scanParams;
	scanHeader.initializeContents();
	if (!scanHeader.writeToStream(outputStream)) {
		DWT.error(__FILE__, __LINE__, DWT.ERROR_IO);
	}
	convertImageToYCbCr(image);
	resetOutputBuffer();
	currentByte = 0;
	currentBitCount = 0;
	encodeScan();
	if (!(new JPEGEndOfImage()).writeToStream(outputStream)) {
		DWT.error(__FILE__, __LINE__, DWT.ERROR_IO);
	}
}
}





abstract class JPEGFixedSizeSegment : JPEGSegment {

	public this() {
		reference = new byte[fixedSize()];
		setSegmentMarker(signature());
	}
	
	public this(byte[] reference) {
		super(reference);
	}
	
	public this(LEDataInputStream byteStream) {
		reference = new byte[fixedSize()];
		try {
			byteStream.read(reference);
		} catch (Exception e) { 
			DWT.error(__FILE__, __LINE__,  e);
		}
	}
	
	abstract public int fixedSize();

	public int getSegmentLength() {
		return fixedSize() - 2;
	}
	
	public void setSegmentLength(int length) {
	}
}






class JPEGFrameHeader : JPEGVariableSizeSegment {
	int maxVFactor;
	int maxHFactor;
	public int[] componentIdentifiers;
	public int[][] componentParameters;

	public this(byte[] reference) {
		super(reference);
	}
	
	public this(LEDataInputStream byteStream) {
		super(byteStream);
		initializeComponentParameters();
	}
	
	public int getSamplePrecision() {
		return reference[4] & 0xFF;
	}
	
	public int getNumberOfLines() {
		return (reference[5] & 0xFF) << 8 | (reference[6] & 0xFF);
	}
	
	public int getSamplesPerLine() {
		return (reference[7] & 0xFF) << 8 | (reference[8] & 0xFF);
	}
	
	public int getNumberOfImageComponents() {
		return reference[9] & 0xFF;
	}
	
	public void setSamplePrecision(int precision) {
		reference[4] = cast(byte)(precision & 0xFF);
	}
	
	public void setNumberOfLines(int anInteger) {
		reference[5] = cast(byte)((anInteger & 0xFF00) >> 8);
		reference[6] = cast(byte)(anInteger & 0xFF);
	}
	
	public void setSamplesPerLine(int samples) {
		reference[7] = cast(byte)((samples & 0xFF00) >> 8);
		reference[8] = cast(byte)(samples & 0xFF);
	}
	
	public void setNumberOfImageComponents(int anInteger) {
		reference[9] = cast(byte)(anInteger & 0xFF);
	}
	
	public int getMaxHFactor() {
		return maxHFactor;
	}
	
	public int getMaxVFactor() {
		return maxVFactor;
	}
	
	public void setMaxHFactor(int anInteger) {
		maxHFactor = anInteger;
	}
	
	public void setMaxVFactor(int anInteger) {
		maxVFactor = anInteger;
	}
	
	/* Used when decoding. */
	void initializeComponentParameters() {
		int nf = getNumberOfImageComponents();
		componentIdentifiers = new int[nf];
		int[][] compSpecParams = null;
		int hmax = 1;
		int vmax = 1;
		for (int i = 0; i < nf; i++) {
			int ofs = i * 3 + 10;
			int ci = reference[ofs] & 0xFF;
			componentIdentifiers[i] = ci;
			int hi = (reference[ofs + 1] & 0xFF) / 16;
			int vi = (reference[ofs + 1] & 0xFF) % 16;
			int tqi = reference[ofs + 2] & 0xFF;
			if (hi > hmax) {
				hmax = hi;
			}
			if (vi > vmax) {
				vmax = vi;
			}
			int[] compParam = new int[5];
			compParam[0] = tqi;
			compParam[1] = hi;
			compParam[2] = vi;
			if (compSpecParams.length <= ci) {
				int[][] newParams = new int[][ci + 1];
				System.arraycopy(compSpecParams, 0, newParams, 0, compSpecParams.length);
				compSpecParams = newParams;
			}
			compSpecParams[ci] = compParam;
		}
		int x = getSamplesPerLine();
		int y = getNumberOfLines();
		int[] intArr1;
		intArr1 ~= 8;
		intArr1 ~= 16;
		intArr1 ~= 24;
		intArr1 ~= 32;
		int[] multiples = intArr1;
		for (int i = 0; i < nf; i++) {
			int[] compParam = compSpecParams[componentIdentifiers[i]];
			int hi = compParam[1];
			int vi = compParam[2];
			int compWidth = (x * hi + hmax - 1) / hmax;
			int compHeight = (y * vi + vmax - 1) / vmax;
			int dsWidth = roundUpToMultiple(compWidth, multiples[hi - 1]);
			int dsHeight = roundUpToMultiple(compHeight, multiples[vi - 1]);
			compParam[3] = dsWidth;
			compParam[4] = dsHeight;
		}
		setMaxHFactor(hmax);
		setMaxVFactor(vmax);
		componentParameters = compSpecParams;
	}
	
	/* Used when encoding. */
	public void initializeContents() {
		int nf = getNumberOfImageComponents();
		if (nf == 0 || nf != componentParameters.length) {
			DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
		}
		int hmax = 0;
		int vmax = 0;
		int[][] compSpecParams = componentParameters;
		for (int i = 0; i < nf; i++) {
			int ofs = i * 3 + 10;
			int[] compParam = compSpecParams[componentIdentifiers[i]];
			int hi = compParam[1];
			int vi = compParam[2];
			if (hi * vi > 4) {
				DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
			}
			reference[ofs] = cast(byte)(i + 1);
			reference[ofs + 1] = cast(byte)(hi * 16 + vi);
			reference[ofs + 2] = cast(byte)(compParam[0]);
			if (hi > hmax) hmax = hi;
			if (vi > vmax) vmax = vi;
		}
		int x = getSamplesPerLine();
		int y = getNumberOfLines();
		int[] intArr2;
		intArr2 ~= 8;
		intArr2 ~= 16;
		intArr2 ~= 24;
		intArr2 ~= 32;
		int[] multiples = intArr2;
		for (int i = 0; i < nf; i++) {
			int[] compParam = compSpecParams[componentIdentifiers[i]];
			int hi = compParam[1];
			int vi = compParam[2];
			int compWidth = (x * hi + hmax - 1) / hmax;
			int compHeight = (y * vi + vmax - 1) / vmax;
			int dsWidth = roundUpToMultiple(compWidth, multiples[hi - 1]);
			int dsHeight = roundUpToMultiple(compHeight, multiples[vi - 1]);
			compParam[3] = dsWidth;
			compParam[4] = dsHeight;
		}
		setMaxHFactor(hmax);
		setMaxVFactor(vmax);
	}
	
	int roundUpToMultiple(int anInteger, int mInteger) {
		int a = anInteger + mInteger - 1;
		return a - (a % mInteger);
	}
	
	/*
	 * Verify the information contained in the receiver is correct.
	 * Answer true if the header contains a valid marker. Otherwise,
	 * answer false. Valid Start Of Frame markers are:
	 *	SOF_0  - Baseline DCT, Huffman coding
	 *	SOF_1  - Extended sequential DCT, Huffman coding
	 *	SOF_2  - Progressive DCT, Huffman coding
	 *	SOF_3  - Lossless (sequential), Huffman coding
	 *	SOF_5  - Differential sequential, Huffman coding
	 *	SOF_6  - Differential progressive, Huffman coding
	 *	SOF_7  - Differential lossless, Huffman coding
	 *	SOF_9  - Extended sequential DCT, arithmetic coding
	 *	SOF_10 - Progressive DCT, arithmetic coding
	 *	SOF_11 - Lossless (sequential), arithmetic coding
	 *	SOF_13 - Differential sequential, arithmetic coding
	 *	SOF_14 - Differential progressive, arithmetic coding
	 *	SOF_15 - Differential lossless, arithmetic coding
	 */
	public boolean verify() {
		int marker = getSegmentMarker();
		return (marker >= JPEGFileFormat.SOF0 && marker <= JPEGFileFormat.SOF3) ||
			(marker >= JPEGFileFormat.SOF5 && marker <= JPEGFileFormat.SOF7) ||
			(marker >= JPEGFileFormat.SOF9 && marker <= JPEGFileFormat.SOF11) ||
			(marker >= JPEGFileFormat.SOF13 && marker <= JPEGFileFormat.SOF15);
	}

	public boolean isProgressive() {
		int marker = getSegmentMarker();
		return marker == JPEGFileFormat.SOF2
			|| marker == JPEGFileFormat.SOF6
			|| marker == JPEGFileFormat.SOF10
			|| marker == JPEGFileFormat.SOF14;
	}
	
	public boolean isArithmeticCoding() {
		return getSegmentMarker() >= JPEGFileFormat.SOF9;
	}
}




/**
 * JPEGHuffmanTable class actually represents two types of object:
 * 1) A DHT (Define Huffman Tables) segment, which may represent
 *	as many as 4 Huffman tables. In this case, the tables are
 *	stored in the allTables array.
 * 2) A single Huffman table. In this case, the allTables array
 *	will be null.
 * The 'reference' field is stored in both types of object, but
 * 'initialize' is only called if the object represents a DHT.
 */
class JPEGHuffmanTable : JPEGVariableSizeSegment {
	JPEGHuffmanTable[] allTables;
	int tableClass;
	int tableIdentifier;
	int[] dhCodes;
	int[] dhCodeLengths;
	int[] dhMaxCodes;
	int[] dhMinCodes;
	int[] dhValPtrs;
	int[] dhValues;
	int[] ehCodes;
	byte[] ehCodeLengths;
static byte[] DCLuminanceTable = [
		cast(byte)255, cast(byte)196, 0, 31, 0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
		0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
	];
	static byte[] DCChrominanceTable = [
		cast(byte)255, cast(byte)196, 0, 31, 1, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
		0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
	];
	static byte[] ACLuminanceTable = [
		cast(byte)255, cast(byte)196, 0, cast(byte)181, 16, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125, 
		1, 2, 3, 0, 4, 17, 5, 18, 33, 49, 65, 6, 19, 81, 97, 7, 34, 113, 20, 
		50, cast(byte)129, cast(byte)145, cast(byte)161, 8, 35, 66, cast(byte)177, cast(byte)193, 21, 82, cast(byte)209, cast(byte)240, 36, 51, 98, 
		114, cast(byte)130, 9, 10, 22, 23, 24, 25, 26, 37, 38, 39, 40, 41, 42, 52, 53, 
		54, 55, 56, 57, 58, 67, 68, 69, 70, 71, 72, 73, 74, 83, 84, 85, 86, 87, 
		88, 89, 90, 99, 100, 101, 102, 103, 104, 105, 106, 115, 116, 117, 118, 
		119, 120, 121, 122, cast(byte)131, cast(byte)132, cast(byte)133, cast(byte)134, cast(byte)135, cast(byte)136, cast(byte)137, cast(byte)138, cast(byte)146, cast(byte)147, cast(byte)148, 
		cast(byte)149, cast(byte)150, cast(byte)151, cast(byte)152, cast(byte)153, cast(byte)154, cast(byte)162, cast(byte)163, cast(byte)164, cast(byte)165, cast(byte)166, cast(byte)167, cast(byte)168, cast(byte)169, cast(byte)170, 
		cast(byte)178, cast(byte)179, cast(byte)180, cast(byte)181, cast(byte)182, cast(byte)183, cast(byte)184, cast(byte)185, cast(byte)186, cast(byte)194, cast(byte)195, cast(byte)196, cast(byte)197, cast(byte)198, cast(byte)199, 
		cast(byte)200, cast(byte)201, cast(byte)202, cast(byte)210, cast(byte)211, cast(byte)212, cast(byte)213, cast(byte)214, cast(byte)215, cast(byte)216, cast(byte)217, cast(byte)218, cast(byte)225, cast(byte)226, cast(byte)227, 
		cast(byte)228, cast(byte)229, cast(byte)230, cast(byte)231, cast(byte)232, cast(byte)233, cast(byte)234, cast(byte)241, cast(byte)242, cast(byte)243, cast(byte)244, cast(byte)245, cast(byte)246, cast(byte)247, cast(byte)248, 
		cast(byte)249, cast(byte)250
	];
	static byte[] ACChrominanceTable = [
		cast(byte)255, cast(byte)196, 0, cast(byte)181, 17, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 
		1, 2, 119, 0, 1, 2, 3, 17, 4, 5, 33, 49, 6, 18, 65, 81, 7, 97, 113, 19, 34, 
		50, cast(byte)129, 8, 20, 66, cast(byte)145, cast(byte)161, cast(byte)177, cast(byte)193, 9, 35, 
		51, 82, cast(byte)240, 21, 98, 114, cast(byte)209, 10, 22, 36, 52, cast(byte)225, 37, 
		cast(byte)241, 23, 24, 25, 26, 38, 39, 40, 41, 42, 53, 54, 55, 56, 57, 58, 67, 
		68, 69, 70, 71, 72, 73, 74, 83, 84, 85, 86, 87, 88, 89, 90, 99, 100, 101, 102, 
		103, 104, 105, 106, 115, 116, 117, 118, 119, 120, 121, 122, cast(byte)130, 
		cast(byte)131, cast(byte)132, cast(byte)133, cast(byte)134, cast(byte)135, cast(byte)136, cast(byte)137, 
		cast(byte)138, cast(byte)146, cast(byte)147, cast(byte)148, cast(byte)149, cast(byte)150, cast(byte)151, 
		cast(byte)152, cast(byte)153, cast(byte)154, cast(byte)162, cast(byte)163, cast(byte)164, cast(byte)165, 
		cast(byte)166, cast(byte)167, cast(byte)168, cast(byte)169, cast(byte)170, cast(byte)178, cast(byte)179, 
		cast(byte)180, cast(byte)181, cast(byte)182, cast(byte)183, cast(byte)184, cast(byte)185, cast(byte)186, 
		cast(byte)194, cast(byte)195, cast(byte)196, cast(byte)197, cast(byte)198, cast(byte)199, cast(byte)200, 
		cast(byte)201, cast(byte)202, cast(byte)210, cast(byte)211, cast(byte)212, cast(byte)213, cast(byte)214, 
		cast(byte)215, cast(byte)216, cast(byte)217, cast(byte)218, cast(byte)226, cast(byte)227, cast(byte)228, 
		cast(byte)229, cast(byte)230, cast(byte)231, cast(byte)232, cast(byte)233, cast(byte)234, cast(byte)242, 
		cast(byte)243, cast(byte)244, cast(byte)245, cast(byte)246, cast(byte)247, cast(byte)248, cast(byte)249, 
		cast(byte)250
	];

	
public this(byte[] reference) {
	super(reference);
}

public this(LEDataInputStream byteStream) {
	super(byteStream);
	initialize();
}

public JPEGHuffmanTable[] getAllTables() {
	return allTables;
}

public static JPEGHuffmanTable getDefaultACChrominanceTable() {
	JPEGHuffmanTable result = new JPEGHuffmanTable(ACChrominanceTable);
	result.initialize();
	return result;
}

public static JPEGHuffmanTable getDefaultACLuminanceTable() {
	JPEGHuffmanTable result = new JPEGHuffmanTable(ACLuminanceTable);
	result.initialize();
	return result;
}

public static JPEGHuffmanTable getDefaultDCChrominanceTable() {
	JPEGHuffmanTable result = new JPEGHuffmanTable(DCChrominanceTable);
	result.initialize();
	return result;
}

public static JPEGHuffmanTable getDefaultDCLuminanceTable() {
	JPEGHuffmanTable result = new JPEGHuffmanTable(DCLuminanceTable);
	result.initialize();
	return result;
}

public int[] getDhMaxCodes() {
	return dhMaxCodes;
}

public int[] getDhMinCodes() {
	return dhMinCodes;
}

public int[] getDhValPtrs() {
	return dhValPtrs;
}

public int[] getDhValues() {
	return dhValues;
}

public int getTableClass() {
	return tableClass;
}

public int getTableIdentifier() {
	return tableIdentifier;
}

void initialize() {
	int totalLength = getSegmentLength() - 2;
	int ofs = 4;
	int[] bits = new int[16];
	JPEGHuffmanTable[] huffTables = new JPEGHuffmanTable[8]; // maximum is 4 AC + 4 DC
	int huffTableCount = 0;
	while (totalLength > 0) {
		int tc = (reference[ofs] & 0xFF) / 16; // table class: AC (1) or DC (0)
		int tid = (reference[ofs] & 0xFF) % 16; // table id: 0-1 baseline, 0-3 prog/ext
		ofs++;
		
		/* Read the 16 count bytes and add them together to get the table size. */
		int count = 0;
		for (int i = 0; i < bits.length; i++) {
			int bCount = reference[ofs + i] & 0xFF;
			bits[i] = bCount;
			count += bCount;
		}
		ofs += 16;
		totalLength -= 17;
		
		/* Read the table. */
		int[] huffVals = new int[count];
		for (int i = 0; i < count; i++) {
			huffVals[i] = reference[ofs + i] & 0xFF;
		}
		ofs += count;
		totalLength -= count;
		
		/* Calculate the lengths. */
		int[] huffCodeLengths = new int[50]; // start with 50 and increment as needed
		int huffCodeLengthsIndex = 0;
		for (int i = 0; i < 16; i++) {
			for (int j = 0; j < bits[i]; j++) {
				if (huffCodeLengthsIndex >= huffCodeLengths.length) {
					int[] newHuffCodeLengths = new int[huffCodeLengths.length + 50];
					System.arraycopy(huffCodeLengths, 0, newHuffCodeLengths, 0, huffCodeLengths.length);
					huffCodeLengths = newHuffCodeLengths;
				}
				huffCodeLengths[huffCodeLengthsIndex] = i + 1;
				huffCodeLengthsIndex++;
			}
		}
		
		/* Truncate huffCodeLengths to the correct size. */
		if (huffCodeLengthsIndex < huffCodeLengths.length) {
			int[] newHuffCodeLengths = new int[huffCodeLengthsIndex];
			System.arraycopy(huffCodeLengths, 0, newHuffCodeLengths, 0, huffCodeLengthsIndex);
			huffCodeLengths = newHuffCodeLengths;
		}
		
		/* Calculate the Huffman codes. */
		int[] huffCodes = new int[50]; // start with 50 and increment as needed
		int huffCodesIndex = 0;
		int k = 1;
		int code = 0;
		int si = huffCodeLengths[0];
		int p = 0;
		while (p < huffCodeLengthsIndex) {
			while ((p < huffCodeLengthsIndex) && (huffCodeLengths[p] == si)) {
				if (huffCodesIndex >= huffCodes.length) {
					int[] newHuffCodes = new int[huffCodes.length + 50];
					System.arraycopy(huffCodes, 0, newHuffCodes, 0, huffCodes.length);
					huffCodes = newHuffCodes;
				}
				huffCodes[huffCodesIndex] = code;
				huffCodesIndex++;
				code++;
				p++;
			}
			code *= 2;
			si++;
		}
		
		/* Truncate huffCodes to the correct size. */
		if (huffCodesIndex < huffCodes.length) {
			int[] newHuffCodes = new int[huffCodesIndex];
			System.arraycopy(huffCodes, 0, newHuffCodes, 0, huffCodesIndex);
			huffCodes = newHuffCodes;
		}
		
		/* Calculate the maximum and minimum codes */
		k = 0;
		int[] maxCodes = new int[16];
		int[] minCodes = new int[16];
		int[] valPtrs = new int[16];
		for (int i = 0; i < 16; i++) {
			int bSize = bits[i];
			if (bSize == 0) {
				maxCodes[i] = -1;
			} else {
				valPtrs[i] = k;
				minCodes[i] = huffCodes[k];
				k += bSize;
				maxCodes[i] = huffCodes[k - 1];
			}
		}
		
		/* Calculate the eHuffman codes and lengths. */
		int[] eHuffCodes = new int[256];
		byte[] eHuffSize = new byte[256];
		for (int i = 0; i < huffCodesIndex; i++) {
			eHuffCodes[huffVals[i]] = huffCodes[i];
			eHuffSize[huffVals[i]] = cast(byte)huffCodeLengths[i];
		}
		
		/* Create the new JPEGHuffmanTable and add it to the allTables array. */
		JPEGHuffmanTable dhtTable = new JPEGHuffmanTable(reference);
		dhtTable.tableClass = tc;
		dhtTable.tableIdentifier = tid;
		dhtTable.dhValues = huffVals;
		dhtTable.dhCodes = huffCodes;
		dhtTable.dhCodeLengths = huffCodeLengths;
		dhtTable.dhMinCodes = minCodes;
		dhtTable.dhMaxCodes = maxCodes;
		dhtTable.dhValPtrs = valPtrs;
		dhtTable.ehCodes = eHuffCodes;
		dhtTable.ehCodeLengths = eHuffSize;
		huffTables[huffTableCount] = dhtTable;
		huffTableCount++;
	}
	allTables = new JPEGHuffmanTable[huffTableCount];
	System.arraycopy(huffTables, 0, allTables, 0, huffTableCount);
}

public int signature() {
	return JPEGFileFormat.DHT;
}
}


class JPEGQuantizationTable : JPEGVariableSizeSegment {
	public static byte[] DefaultLuminanceQTable = [
		cast(byte)255, cast(byte)219, 0, 67, 0,
		16, 11, 10, 16, 24, 40, 51, 61,
		12, 12, 14, 19, 26, 58, 60, 55,
		14, 13, 16, 24, 40, 57, 69, 56,
		14, 17, 22, 29, 51, 87, 80, 62,
		18, 22, 37, 56, 68, 109, 103, 77,
		24, 35, 55, 64, 81, 104, 113, 92,
		49, 64, 78, 87, 103, 121, 120, 101,
		72, 92, 95, 98, 112, 100, 103, 99
	];
	public static byte[] DefaultChrominanceQTable = [
		cast(byte)255, cast(byte)219, 0, 67, 1,
		17, 18, 24, 47, 99, 99, 99, 99,
		18, 21, 26, 66, 99, 99, 99, 99,
		24, 26, 56, 99, 99, 99, 99, 99,
		47, 66, 99, 99, 99, 99, 99, 99,
		99, 99, 99, 99, 99, 99, 99, 99,
		99, 99, 99, 99, 99, 99, 99, 99,
		99, 99, 99, 99, 99, 99, 99, 99,
		99, 99, 99, 99, 99, 99, 99, 99
	];
	
public this(byte[] reference) {
	super(reference);
}

public this(LEDataInputStream byteStream) {
	super(byteStream);
}

public static JPEGQuantizationTable defaultChrominanceTable() {
	byte[] data = new byte[DefaultChrominanceQTable.length];
	System.arraycopy(DefaultChrominanceQTable, 0, data, 0, data.length);
	return new JPEGQuantizationTable(data);
}

public static JPEGQuantizationTable defaultLuminanceTable() {
	byte[] data = new byte[DefaultLuminanceQTable.length];
	System.arraycopy(DefaultLuminanceQTable, 0, data, 0, data.length);
	return new JPEGQuantizationTable(data);
}

public int[] getQuantizationTablesKeys() {
	int[] keys = new int[4];
	int keysIndex = 0;
	int totalLength = getSegmentLength() - 2;
	int ofs = 4;
	while (totalLength > 64) {
		int tq = (reference[ofs] & 0xFF) % 16;
		int pq = (reference[ofs] & 0xFF) / 16;
		if (pq == 0) {
			ofs += 65;
			totalLength -= 65;
		} else {
			ofs += 129;
			totalLength -= 129;
		}
		if (keysIndex >= keys.length) {
			int[] newKeys = new int[keys.length + 4];
			System.arraycopy(keys, 0, newKeys, 0, keys.length);
			keys = newKeys;
		}
		keys[keysIndex] = tq;
		keysIndex++;
	}
	int[] newKeys = new int[keysIndex];
	System.arraycopy(keys, 0, newKeys, 0, keysIndex);
	return newKeys;
}

public int[][] getQuantizationTablesValues() {
	int[][] values = new int[][4];
	int valuesIndex = 0;
	int totalLength = getSegmentLength() - 2;
	int ofs = 4;
	while (totalLength > 64) {
		int[] qk = new int[64];
		int pq = (reference[ofs] & 0xFF) / 16;
		if (pq == 0) {
			for (int i = 0; i < qk.length; i++) {
				qk[i] = reference[ofs + i + 1];
			}
			ofs += 65;
			totalLength -= 65;
		} else {
			for (int i = 0; i < qk.length; i++) {
				int idx = (i - 1) * 2 ;
				qk[i] = (reference[ofs + idx + 1] & 0xFF) * 256 + (reference[ofs + idx + 2] & 0xFF);
			}
			ofs += 129;
			totalLength -= 129;
		}
		if (valuesIndex >= values.length) {
			int[][] newValues = new int[][values.length + 4];
			System.arraycopy(values, 0, newValues, 0, values.length);
			values = newValues;
		}
		values[valuesIndex] = qk;
		valuesIndex++;
	}
	int[][] newValues = new int[][valuesIndex];
	System.arraycopy(values, 0, newValues, 0, valuesIndex);
	return newValues;
}

public void scaleBy(int qualityFactor) {
	int qFactor = qualityFactor;
	if (qFactor <= 0) {
		qFactor = 1;
	}
	if (qFactor > 100) {
		qFactor = 100;
	}
	if (qFactor < 50) {
		qFactor = 5000 / qFactor;
	} else {
		qFactor = 200 - (qFactor * 2);
	}
	int totalLength = getSegmentLength() - 2;
	int ofs = 4;
	while (totalLength > 64) {
//		int tq = (reference[ofs] & 0xFF) % 16;
		int pq = (reference[ofs] & 0xFF) / 16;
		if (pq == 0) {
			for (int i = ofs + 1; i <= ofs + 64; i++) {
				int temp = ((reference[i] & 0xFF) * qFactor + 50) / 100;
				if (temp <= 0) temp = 1;
				if (temp > 255) temp = 255;
				reference[i] = cast(byte)temp;
			}
			ofs += 65;
			totalLength -= 65;
		} else {
			for (int i = ofs + 1; i <= ofs + 128; i += 2) {
				int temp = (((reference[i] & 0xFF) * 256 + (reference[i + 1] & 0xFF)) * qFactor + 50) / 100;
				if (temp <= 0) temp = 1;
				if (temp > 32767) temp = 32767;
				reference[i] = cast(byte)(temp / 256);
				reference[i + 1] = cast(byte)(temp % 256);
			}
			ofs += 129;
			totalLength -= 129;
		}
	}
}

public int signature() {
	return JPEGFileFormat.DQT;
}
}




class JPEGRestartInterval : JPEGFixedSizeSegment {

	public this(LEDataInputStream byteStream) {
		super(byteStream);
	}
	
	public int signature() {
		return JPEGFileFormat.DRI;
	}
	
	public int getRestartInterval() {
		return ((reference[4] & 0xFF) << 8 | (reference[5] & 0xFF));
	}

	public int fixedSize() {
		return 6;
	}
}




class JPEGScanHeader : JPEGVariableSizeSegment {
	public int[][] componentParameters;

public this(byte[] reference) {
	super(reference);
}

public this(LEDataInputStream byteStream) {
	super(byteStream);
	initializeComponentParameters();
}

public int getApproxBitPositionHigh() {
	return (reference[(2 * getNumberOfImageComponents()) + 7] & 0xFF) / 16;
}

public int getApproxBitPositionLow() {
	return (reference[(2 * getNumberOfImageComponents()) + 7] & 0xFF) % 16;
}

public int getEndOfSpectralSelection() {
	return (reference[(2 * getNumberOfImageComponents()) + 6] & 0xFF);
}

public int getNumberOfImageComponents() {
	return (reference[4] & 0xFF);
}

public int getStartOfSpectralSelection() {
	return (reference[(2 * getNumberOfImageComponents()) + 5] & 0xFF);
}

/* Used when decoding. */
void initializeComponentParameters() {
	int compCount = getNumberOfImageComponents();
	componentParameters = null;
	for (int i = 0; i < compCount; i++) {
		int ofs = 5 + i * 2;
		int cid = reference[ofs] & 0xFF;
		int dc = (reference[ofs + 1] & 0xFF) / 16;
		int ac = (reference[ofs + 1] & 0xFF) % 16;
		if (componentParameters.length <= cid) {
			int[][] newParams = new int[][cid + 1];
			System.arraycopy(componentParameters, 0, newParams, 0, componentParameters.length);
			componentParameters = newParams;
		}
		int[] intArr;
		intArr ~= dc;
		intArr ~= ac;
		componentParameters[cid] = intArr;
	}
}

/* Used when encoding. */
public void initializeContents() {
	int compCount = getNumberOfImageComponents();
	int[][] compSpecParams = componentParameters;
	if (compCount == 0 || compCount != compSpecParams.length) {
		DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
	}
	for (int i = 0; i < compCount; i++) {
		int ofs = i * 2 + 5;
		int[] compParams = compSpecParams[i];
		reference[ofs] = cast(byte)(i + 1);
		reference[ofs + 1] = cast(byte)(compParams[0] * 16 + compParams[1]);
	}
}

public void setEndOfSpectralSelection(int anInteger) {
	reference[(2 * getNumberOfImageComponents()) + 6] = cast(byte)anInteger;
}

public void setNumberOfImageComponents(int anInteger) {
	reference[4] = cast(byte)(anInteger & 0xFF);
}

public void setStartOfSpectralSelection(int anInteger) {
	reference[(2 * getNumberOfImageComponents()) + 5] = cast(byte)anInteger;
}

public int signature() {
	return JPEGFileFormat.SOS;
}

public boolean verifyProgressiveScan() {
	int start = getStartOfSpectralSelection();
	int end = getEndOfSpectralSelection();
	int low = getApproxBitPositionLow();
	int high = getApproxBitPositionHigh();
	int count = getNumberOfImageComponents();
	if ((start == 0 && end == 00) || (start <= end && end <= 63)) {
		if (low <= 13 && high <= 13 && (high == 0 || high == low + 1)) {
			return start == 0 || (start > 0 && count == 1);
		}
	}
	return false;
}

public boolean isACProgressiveScan() {
	return getStartOfSpectralSelection() != 0 && getEndOfSpectralSelection() != 0;
}

public boolean isDCProgressiveScan() {
	return getStartOfSpectralSelection() == 0 && getEndOfSpectralSelection() == 0;
}

public boolean isFirstScan() {
	return getApproxBitPositionHigh() == 0;
}

}



class JPEGSegment {
	public byte[] reference;

	this() {
	}
	
	public this(byte[] reference) {
		this.reference = reference;
	}
	
	public int signature() {
		return 0;
	}
	
	public boolean verify() {
		return getSegmentMarker() == signature();
	}
	
	public int getSegmentMarker() {
		return ((reference[0] & 0xFF) << 8 | (reference[1] & 0xFF));
	}
	
	public void setSegmentMarker(int marker) {
		reference[0] = cast(byte)((marker & 0xFF00) >> 8);
		reference[1] = cast(byte)(marker & 0xFF);
	}
	
	public int getSegmentLength() {
		return ((reference[2] & 0xFF) << 8 | (reference[3] & 0xFF));
	}
	
	public void setSegmentLength(int length) {
		reference[2] = cast(byte)((length & 0xFF00) >> 8);
		reference[3] = cast(byte)(length & 0xFF);
	}
	
	public boolean writeToStream(LEDataOutputStream byteStream) {
		try {
			byteStream.write(reference);
			return true;
		} catch (Exception e) {
			return false;
		}
	}
}




class JPEGStartOfImage : JPEGFixedSizeSegment {

	public this() {
		super();
	}
	
	public this(byte[] reference) {
		super(reference);
	}
	
	public this(LEDataInputStream byteStream) {
		super(byteStream);
	}
	
	public int signature() {
		return JPEGFileFormat.SOI;
	}
	
	public int fixedSize() {
		return 2;
	}
}



abstract class JPEGVariableSizeSegment : JPEGSegment {

	public this(byte[] reference) {
		super(reference);
	}
	
	public this(LEDataInputStream byteStream) {
		try {
			byte[] header = new byte[4];
			byteStream.read(header);
			reference = header; // to use getSegmentLength()
			byte[] contents = new byte[getSegmentLength() + 2];
			contents[0] = header[0];
			contents[1] = header[1];
			contents[2] = header[2];
			contents[3] = header[3];
			byteStream.readBytes(contents, 4, contents.length - 4);
			reference = contents;
		} catch (Exception e) {
			DWT.error(__FILE__, __LINE__,  e);
		}
	}
}




